Bug 1038238 - Part 1: Make Error instances use SavedFrame objects for their
authorNick Fitzgerald <fitzgen@gmail.com>
Fri, 27 Mar 2015 13:08:46 -0700
changeset 266598 550a5c9e8800868198536792e43b872bde3576fe
parent 266597 fadf551562b882a3265ea0e10eee74bb028bfcd9
child 266599 ba50784c810d71cf43ee6c519896f749aa259637
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1038238
milestone39.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 1038238 - Part 1: Make Error instances use SavedFrame objects for their stacks; r=jorendorff
dom/promise/PromiseCallback.cpp
dom/workers/ServiceWorkerManager.cpp
js/src/jit-test/tests/basic/error-stack-accessors.js
js/src/jit-test/tests/basic/shell-principals.js
js/src/jit-test/tests/basic/test-error-accessors-with-wrappers.js
js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js
js/src/jsapi-tests/testSavedStacks.cpp
js/src/jsapi-tests/testUncaughtError.cpp
js/src/jsapi.h
js/src/jsexn.cpp
js/src/vm/ErrorObject-inl.h
js/src/vm/ErrorObject.cpp
js/src/vm/ErrorObject.h
js/src/vm/SavedStacks-inl.h
js/src/vm/SavedStacks.cpp
js/src/vm/SavedStacks.h
js/xpconnect/tests/chrome/test_xrayToJS.xul
js/xpconnect/wrappers/XrayWrapper.cpp
toolkit/devtools/server/tests/unit/test_objectgrips-11.js
--- a/dom/promise/PromiseCallback.cpp
+++ b/dom/promise/PromiseCallback.cpp
@@ -260,17 +260,16 @@ WrapperPromiseCallback::Call(JSContext* 
           if (script) {
             fileName = JS_GetScriptFilename(script);
             lineNumber = JS_GetScriptBaseLineNumber(aCx, script);
           }
         }
       }
 
       // We're back in aValue's compartment here.
-      JS::Rooted<JSString*> stack(aCx, JS_GetEmptyString(JS_GetRuntime(aCx)));
       JS::Rooted<JSString*> fn(aCx, JS_NewStringCopyZ(aCx, fileName));
       if (!fn) {
         // Out of memory. Promise will stay unresolved.
         JS_ClearPendingException(aCx);
         return;
       }
 
       JS::Rooted<JSString*> message(aCx,
@@ -278,17 +277,17 @@ WrapperPromiseCallback::Call(JSContext* 
           "then() cannot return same Promise that it resolves."));
       if (!message) {
         // Out of memory. Promise will stay unresolved.
         JS_ClearPendingException(aCx);
         return;
       }
 
       JS::Rooted<JS::Value> typeError(aCx);
-      if (!JS::CreateError(aCx, JSEXN_TYPEERR, stack, fn, lineNumber, 0,
+      if (!JS::CreateError(aCx, JSEXN_TYPEERR, JS::NullPtr(), fn, lineNumber, 0,
                            nullptr, message, &typeError)) {
         // Out of memory. Promise will stay unresolved.
         JS_ClearPendingException(aCx);
         return;
       }
 
       mNextPromise->RejectInternal(aCx, typeError);
       return;
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -375,18 +375,16 @@ public:
   void
   UpdateFailed(const ErrorEventInit& aErrorDesc) override
   {
     AutoJSAPI jsapi;
     jsapi.Init(mWindow);
 
     JSContext* cx = jsapi.cx();
 
-    JS::Rooted<JSString*> stack(cx, JS_GetEmptyString(JS_GetRuntime(cx)));
-
     JS::Rooted<JS::Value> fnval(cx);
     if (!ToJSValue(cx, aErrorDesc.mFilename, &fnval)) {
       JS_ClearPendingException(cx);
       mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
       return;
     }
     JS::Rooted<JSString*> fn(cx, fnval.toString());
 
@@ -394,17 +392,17 @@ public:
     if (!ToJSValue(cx, aErrorDesc.mMessage, &msgval)) {
       JS_ClearPendingException(cx);
       mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
       return;
     }
     JS::Rooted<JSString*> msg(cx, msgval.toString());
 
     JS::Rooted<JS::Value> error(cx);
-    if (!JS::CreateError(cx, JSEXN_ERR, stack, fn, aErrorDesc.mLineno,
+    if (!JS::CreateError(cx, JSEXN_ERR, JS::NullPtr(), fn, aErrorDesc.mLineno,
                          aErrorDesc.mColno, nullptr, msg, &error)) {
       JS_ClearPendingException(cx);
       mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
       return;
     }
 
     mPromise->MaybeReject(cx, error);
   }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/error-stack-accessors.js
@@ -0,0 +1,31 @@
+// Test the Error.prototype.stack getter/setter with various "fun" edge cases.
+
+load(libdir + "asserts.js");
+
+// Stack should be accessible in subclasses. The accessor should walk up the
+// prototype chain.
+assertEq(typeof Object.create(Error()).stack, "string");
+assertEq(Object.create(Error.prototype).stack, "");
+
+// Stack should be overridable in subclasses.
+{
+  let myError = Object.create(Error());
+  myError.stack = 5;
+  assertEq(myError.stack, 5);
+
+  let myOtherError = Object.create(Error.prototype);
+  myOtherError.stack = 2;
+  assertEq(myOtherError.stack, 2);
+}
+
+// Should throw when there is no Error in the `this` object's prototype chain.
+var obj = Object.create(null);
+var desc = Object.getOwnPropertyDescriptor(Error.prototype, "stack");
+Object.defineProperty(obj, "stack", desc);
+assertThrowsInstanceOf(() => obj.stack, TypeError);
+
+// Should throw with non-object `this` values.
+assertThrowsInstanceOf(desc.set,                TypeError);
+assertThrowsInstanceOf(desc.set.bind("string"), TypeError);
+assertThrowsInstanceOf(desc.get,                TypeError);
+assertThrowsInstanceOf(desc.get.bind("string"), TypeError);
--- a/js/src/jit-test/tests/basic/shell-principals.js
+++ b/js/src/jit-test/tests/basic/shell-principals.js
@@ -25,17 +25,20 @@ function check(expected, stack) {
 var low = newGlobal({ principal: 0 });
 var mid = newGlobal({ principal: 0xffff });
 var high = newGlobal({ principal: 0xfffff });
 
      eval('function a() { check("a",        Error().stack); b(); }');
 low .eval('function b() { check("b",        Error().stack); c(); }');
 mid .eval('function c() { check("cba",      Error().stack); d(); }');
 high.eval('function d() { check("dcba",     Error().stack); e(); }');
-     eval('function e() { check("edcba",    Error().stack); f(); }'); // no principal, so checks skipped
+
+// Globals created with no explicit principals get 0xffff.
+     eval('function e() { check("ecba",     Error().stack); f(); }');
+
 low .eval('function f() { check("fb",       Error().stack); g(); }');
 mid .eval('function g() { check("gfecba",   Error().stack); h(); }');
 high.eval('function h() { check("hgfedcba", Error().stack);      }');
 
 // Make everyone's functions visible to each other, as needed.
      b = low .b;
 low .c = mid .c;
 mid .d = high.d;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/test-error-accessors-with-wrappers.js
@@ -0,0 +1,11 @@
+let g = newGlobal();
+
+let error = g.eval("Error()");
+
+// This should not throw.
+assertEq(typeof error.stack, "string");
+
+g.error = Error();
+
+// Nor should this.
+assertEq(g.eval("typeof error.stack"), "string");
--- a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-08.js
@@ -16,15 +16,15 @@ root.eval([
 
 dbg.memory.trackingAllocationSites = true;
 
 root.doFirstAlloc();
 let allocs1 = dbg.memory.drainAllocationsLog();
 root.doSecondAlloc();
 let allocs2 = dbg.memory.drainAllocationsLog();
 
-let allocs1Lines = allocs1.map(x => x.frame.line);
+let allocs1Lines = allocs1.filter(x => !!x.frame).map(x => x.frame.line);
 assertEq(allocs1Lines.indexOf(root.firstAllocLine) != -1, true);
 assertEq(allocs1Lines.indexOf(root.secondAllocLine) == -1, true);
 
-let allocs2Lines = allocs2.map(x => x.frame.line);
+let allocs2Lines = allocs2.filter(x => !!x.frame).map(x => x.frame.line);
 assertEq(allocs2Lines.indexOf(root.secondAllocLine) != -1, true);
 assertEq(allocs2Lines.indexOf(root.firstAllocLine) == -1, true);
--- a/js/src/jsapi-tests/testSavedStacks.cpp
+++ b/js/src/jsapi-tests/testSavedStacks.cpp
@@ -52,15 +52,14 @@ BEGIN_TEST(testSavedStacks_ApiDefaultVal
 
     // Parent
     JS::RootedObject parent(cx);
     result = JS::GetSavedFrameParent(cx, savedFrame, &parent);
     CHECK(result == JS::SavedFrameResult::AccessDenied);
     CHECK(parent.get() == nullptr);
 
     // Stack string
-    result = JS::GetSavedFrameSource(cx, savedFrame, &str);
-    CHECK(result == JS::SavedFrameResult::AccessDenied);
+    CHECK(JS::StringifySavedFrameStack(cx, savedFrame, &str));
     CHECK(str.get() == cx->runtime()->emptyString);
 
     return true;
 }
 END_TEST(testSavedStacks_ApiDefaultValues)
--- a/js/src/jsapi-tests/testUncaughtError.cpp
+++ b/js/src/jsapi-tests/testUncaughtError.cpp
@@ -17,17 +17,17 @@ BEGIN_TEST(testUncaughtError)
 
     CHECK(uncaughtCount == 0);
 
     Rooted<JSString*> empty(cx, JS_GetEmptyString(JS_GetRuntime(cx)));
     if (!empty)
         return false;
 
     Rooted<Value> err(cx);
-    if (!CreateError(cx, JSEXN_TYPEERR, empty, empty, 0, 0, nullptr, empty, &err))
+    if (!CreateError(cx, JSEXN_TYPEERR, JS::NullPtr(), empty, 0, 0, nullptr, empty, &err))
         return false;
 
     Rooted<JSObject*> errObj(cx, &err.toObject());
     if (!JS_SetProperty(cx, errObj, "fileName", err))
         return false;
     if (!JS_SetProperty(cx, errObj, "lineNumber", err))
         return false;
     if (!JS_SetProperty(cx, errObj, "columnNumber", err))
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4548,17 +4548,17 @@ extern JS_PUBLIC_API(JSErrorReporter)
 JS_GetErrorReporter(JSRuntime *rt);
 
 extern JS_PUBLIC_API(JSErrorReporter)
 JS_SetErrorReporter(JSRuntime *rt, JSErrorReporter er);
 
 namespace JS {
 
 extern JS_PUBLIC_API(bool)
-CreateError(JSContext *cx, JSExnType type, HandleString stack,
+CreateError(JSContext *cx, JSExnType type, HandleObject stack,
             HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
             JSErrorReport *report, HandleString message, MutableHandleValue rval);
 
 /************************************************************************/
 
 /*
  * Weak Maps.
  */
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -24,37 +24,44 @@
 #include "jsscript.h"
 #include "jstypes.h"
 #include "jsutil.h"
 #include "jswrapper.h"
 
 #include "gc/Marking.h"
 #include "vm/ErrorObject.h"
 #include "vm/GlobalObject.h"
+#include "vm/SavedStacks.h"
 #include "vm/StringBuffer.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/ErrorObject-inl.h"
+#include "vm/SavedStacks-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayLength;
 using mozilla::PodArrayZero;
 
 static void
 exn_finalize(FreeOp *fop, JSObject *obj);
 
 bool
 Error(JSContext *cx, unsigned argc, Value *vp);
 
 static bool
 exn_toSource(JSContext *cx, unsigned argc, Value *vp);
 
+static const JSPropertySpec exception_properties[] = {
+    JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0),
+    JS_PS_END
+};
+
 static const JSFunctionSpec exception_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str, exn_toSource, 0, 0),
 #endif
     JS_SELF_HOSTED_FN(js_toString_str, "ErrorToString", 0,0),
     JS_FS_END
 };
 
@@ -77,17 +84,17 @@ static const JSFunctionSpec exception_me
         nullptr,                 /* construct   */ \
         nullptr,                 /* trace       */ \
         { \
             ErrorObject::createConstructor, \
             ErrorObject::createProto, \
             nullptr, \
             nullptr, \
             exception_methods, \
-            nullptr, \
+            exception_properties, \
             nullptr, \
             JSProto_Error \
         } \
     }
 
 const Class
 ErrorObject::classes[JSEXN_LIMIT] = {
     {
@@ -108,17 +115,18 @@ ErrorObject::classes[JSEXN_LIMIT] = {
         nullptr,                 /* construct   */
         nullptr,                 /* trace       */
         {
             ErrorObject::createConstructor,
             ErrorObject::createProto,
             nullptr,
             nullptr,
             exception_methods,
-            0
+            exception_properties,
+            nullptr
         }
     },
     IMPLEMENT_ERROR_SUBCLASS(InternalError),
     IMPLEMENT_ERROR_SUBCLASS(EvalError),
     IMPLEMENT_ERROR_SUBCLASS(RangeError),
     IMPLEMENT_ERROR_SUBCLASS(ReferenceError),
     IMPLEMENT_ERROR_SUBCLASS(SyntaxError),
     IMPLEMENT_ERROR_SUBCLASS(TypeError),
@@ -347,16 +355,26 @@ js::ErrorFromException(JSContext *cx, Ha
     // will fail if they can't unwrap it.
     RootedObject obj(cx, UncheckedUnwrap(objArg));
     if (!obj->is<ErrorObject>())
         return nullptr;
 
     return obj->as<ErrorObject>().getOrCreateErrorReport(cx);
 }
 
+// Cut off the stack if it gets too deep (most commonly for infinite recursion
+// errors).
+static const size_t MAX_REPORTED_STACK_DEPTH = 1u << 7;
+
+static bool
+CaptureStack(JSContext *cx, MutableHandleObject stack)
+{
+    return CaptureCurrentStack(cx, stack, MAX_REPORTED_STACK_DEPTH);
+}
+
 bool
 Error(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Compute the error message, if any. */
     RootedString message(cx, nullptr);
     if (args.hasDefined(0)) {
@@ -386,18 +404,18 @@ Error(JSContext *cx, unsigned argc, Valu
     uint32_t lineNumber, columnNumber = 0;
     if (args.length() > 2) {
         if (!ToUint32(cx, args[2], &lineNumber))
             return false;
     } else {
         lineNumber = iter.done() ? 0 : iter.computeLine(&columnNumber);
     }
 
-    Rooted<JSString*> stack(cx, ComputeStackString(cx));
-    if (!stack)
+    RootedObject stack(cx);
+    if (!CaptureStack(cx, &stack))
         return false;
 
     /*
      * 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.
      */
@@ -497,17 +515,17 @@ ErrorObject::createProto(JSContext *cx, 
 {
     RootedObject errorProto(cx, GenericCreatePrototype(cx, key));
     if (!errorProto)
         return nullptr;
 
     Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
     RootedString emptyStr(cx, cx->names().empty);
     JSExnType type = ExnTypeFromProtoKey(key);
-    if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr))
+    if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, NullPtr(), 0, 0, emptyStr))
         return nullptr;
 
     // The various prototypes also have .name in addition to the normal error
     // instance properties.
     RootedPropertyName name(cx, ClassName(key, cx));
     RootedValue nameValue(cx, StringValue(name));
     if (!DefineProperty(cx, err, cx->names().name, nameValue, nullptr, nullptr, 0))
         return nullptr;
@@ -578,18 +596,18 @@ js::ErrorToException(JSContext *cx, cons
 
     RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
     if (!fileName)
         return cx->isExceptionPending();
 
     uint32_t lineNumber = reportp->lineno;
     uint32_t columnNumber = reportp->column;
 
-    RootedString stack(cx, ComputeStackString(cx));
-    if (!stack)
+    RootedObject stack(cx);
+    if (!CaptureStack(cx, &stack))
         return cx->isExceptionPending();
 
     js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
     if (!report)
         return cx->isExceptionPending();
 
     RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
                                                    lineNumber, columnNumber, &report, messageStr));
@@ -917,34 +935,36 @@ js::CopyErrorObject(JSContext *cx, Handl
     }
 
     RootedString message(cx, err->getMessage());
     if (message && !cx->compartment()->wrap(cx, &message))
         return nullptr;
     RootedString fileName(cx, err->fileName(cx));
     if (!cx->compartment()->wrap(cx, &fileName))
         return nullptr;
-    RootedString stack(cx, err->stack(cx));
+    RootedObject stack(cx, err->stack());
     if (!cx->compartment()->wrap(cx, &stack))
         return nullptr;
     uint32_t lineNumber = err->lineNumber();
     uint32_t columnNumber = err->columnNumber();
     JSExnType errorType = err->type();
 
     // Create the Error object.
     return ErrorObject::create(cx, errorType, stack, fileName,
                                lineNumber, columnNumber, &copyReport, message);
 }
 
 JS_PUBLIC_API(bool)
-JS::CreateError(JSContext *cx, JSExnType type, HandleString stack, HandleString fileName,
+JS::CreateError(JSContext *cx, JSExnType type, HandleObject stack, HandleString fileName,
                     uint32_t lineNumber, uint32_t columnNumber, JSErrorReport *report,
                     HandleString message, MutableHandleValue rval)
 {
     assertSameCompartment(cx, stack, fileName, message);
+    AssertObjectIsSavedFrameOrWrapper(cx, stack);
+
     js::ScopedJSFreePtr<JSErrorReport> rep;
     if (report)
         rep = CopyErrorReport(cx, report);
 
     RootedObject obj(cx,
         js::ErrorObject::create(cx, type, stack, fileName,
                                 lineNumber, columnNumber, &rep, message));
     if (!obj)
--- a/js/src/vm/ErrorObject-inl.h
+++ b/js/src/vm/ErrorObject-inl.h
@@ -27,18 +27,15 @@ js::ErrorObject::lineNumber() const
 
 inline uint32_t
 js::ErrorObject::columnNumber() const
 {
     const HeapSlot &slot = getReservedSlotRef(COLUMNNUMBER_SLOT);
     return slot.isInt32() ? slot.toInt32() : 0;
 }
 
-inline JSString *
-js::ErrorObject::stack(JSContext *cx) const
+inline JSObject *
+js::ErrorObject::stack() const
 {
-    const HeapSlot &slot = getReservedSlotRef(STACK_SLOT);
-    if (slot.isString())
-        return slot.toString();
-    return cx->names().empty;
+    return getReservedSlotRef(STACK_SLOT).toObjectOrNull();
 }
 
 #endif /* vm_ErrorObject_inl_h */
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -4,45 +4,48 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/ErrorObject-inl.h"
 
 #include "jsexn.h"
 
+#include "js/CallArgs.h"
 #include "vm/GlobalObject.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
+#include "vm/SavedStacks-inl.h"
 #include "vm/Shape-inl.h"
 
 using namespace js;
 
 /* static */ Shape *
 js::ErrorObject::assignInitialShape(ExclusiveContext *cx, Handle<ErrorObject*> obj)
 {
     MOZ_ASSERT(obj->empty());
 
     if (!obj->addDataProperty(cx, cx->names().fileName, FILENAME_SLOT, 0))
         return nullptr;
     if (!obj->addDataProperty(cx, cx->names().lineNumber, LINENUMBER_SLOT, 0))
         return nullptr;
-    if (!obj->addDataProperty(cx, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0))
-        return nullptr;
-    return obj->addDataProperty(cx, cx->names().stack, STACK_SLOT, 0);
+    return obj->addDataProperty(cx, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0);
 }
 
 /* static */ bool
 js::ErrorObject::init(JSContext *cx, Handle<ErrorObject*> obj, JSExnType type,
                       ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName,
-                      HandleString stack, uint32_t lineNumber, uint32_t columnNumber,
+                      HandleObject stack, uint32_t lineNumber, uint32_t columnNumber,
                       HandleString message)
 {
+    AssertObjectIsSavedFrameOrWrapper(cx, stack);
+    assertSameCompartment(cx, obj, stack);
+
     // Null out early in case of error, for exn_finalize's sake.
     obj->initReservedSlot(ERROR_REPORT_SLOT, PrivateValue(nullptr));
 
     if (!EmptyShape::ensureInitialCustomShape<ErrorObject>(cx, obj))
         return false;
 
     // The .message property isn't part of the initial shape because it's
     // present in some error objects -- |Error.prototype|, |new Error("f")|,
@@ -55,40 +58,41 @@ js::ErrorObject::init(JSContext *cx, Han
             return false;
         MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT);
     }
 
     MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().fileName))->slot() == FILENAME_SLOT);
     MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().lineNumber))->slot() == LINENUMBER_SLOT);
     MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().columnNumber))->slot() ==
                COLUMNNUMBER_SLOT);
-    MOZ_ASSERT(obj->lookupPure(NameToId(cx->names().stack))->slot() == STACK_SLOT);
     MOZ_ASSERT_IF(message,
                   obj->lookupPure(NameToId(cx->names().message))->slot() == MESSAGE_SLOT);
 
     MOZ_ASSERT(JSEXN_ERR <= type && type < JSEXN_LIMIT);
 
     JSErrorReport *report = errorReport ? errorReport->forget() : nullptr;
     obj->initReservedSlot(EXNTYPE_SLOT, Int32Value(type));
+    obj->initReservedSlot(STACK_SLOT, ObjectOrNullValue(stack));
     obj->setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(report));
     obj->initReservedSlot(FILENAME_SLOT, StringValue(fileName));
     obj->initReservedSlot(LINENUMBER_SLOT, Int32Value(lineNumber));
     obj->initReservedSlot(COLUMNNUMBER_SLOT, Int32Value(columnNumber));
-    obj->initReservedSlot(STACK_SLOT, StringValue(stack));
     if (message)
         obj->setSlotWithType(cx, messageShape, StringValue(message));
 
     return true;
 }
 
 /* static */ ErrorObject *
-js::ErrorObject::create(JSContext *cx, JSExnType errorType, HandleString stack,
+js::ErrorObject::create(JSContext *cx, JSExnType errorType, HandleObject stack,
                         HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
                         ScopedJSFreePtr<JSErrorReport> *report, HandleString message)
 {
+    AssertObjectIsSavedFrameOrWrapper(cx, stack);
+
     Rooted<JSObject*> proto(cx, 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);
@@ -144,8 +148,98 @@ js::ErrorObject::getOrCreateErrorReport(
 
     // Cache and return.
     JSErrorReport *copy = CopyErrorReport(cx, &report);
     if (!copy)
         return nullptr;
     setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy));
     return copy;
 }
+
+/* static */ bool
+js::ErrorObject::checkAndUnwrapThis(JSContext *cx, CallArgs &args, const char *fnName,
+                                    MutableHandle<ErrorObject*> error)
+{
+    const Value &thisValue = args.thisv();
+
+    if (!thisValue.isObject()) {
+        JS_ReportErrorNumber(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()));
+    if (!target) {
+        JS_ReportError(cx, "Permission denied to access object");
+        return false;
+    }
+
+    RootedObject proto(cx);
+    while (!target->is<ErrorObject>()) {
+        if (!GetPrototype(cx, target, &proto))
+            return false;
+
+        if (!proto) {
+            // We walked the whole prototype chain and did not find an Error
+            // object.
+            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
+                                 js_Error_str, fnName, thisValue.toObject().getClass()->name);
+            return false;
+        }
+
+        target = CheckedUnwrap(proto);
+        if (!target) {
+            JS_ReportError(cx, "Permission denied to access object");
+            return false;
+        }
+    }
+
+    error.set(&target->as<ErrorObject>());
+    return true;
+}
+
+/* static */ bool
+js::ErrorObject::getStack(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    Rooted<ErrorObject*> error(cx);
+    if (!checkAndUnwrapThis(cx, args, "(get stack)", &error))
+        return false;
+
+    RootedObject savedFrameObj(cx, error->stack());
+    RootedString stackString(cx);
+    if (!StringifySavedFrameStack(cx, savedFrameObj, &stackString))
+        return false;
+    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, CallArgs args)
+{
+    const Value &thisValue = args.thisv();
+    MOZ_ASSERT(thisValue.isObject());
+    RootedObject thisObj(cx, &thisValue.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
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_ErrorObject_h_
 #define vm_ErrorObject_h_
 
 #include "mozilla/ArrayUtils.h"
 
 #include "vm/NativeObject.h"
+#include "vm/SavedStacks.h"
 #include "vm/Shape.h"
 
 struct JSExnPrivate;
 
 namespace js {
 
 /*
  * Initialize the exception constructor/prototype hierarchy.
@@ -31,27 +32,30 @@ class ErrorObject : public NativeObject
     createConstructor(JSContext *cx, JSProtoKey key);
 
     /* For access to createProto. */
     friend JSObject *
     js::InitExceptionClasses(JSContext *cx, HandleObject global);
 
     static bool
     init(JSContext *cx, Handle<ErrorObject*> obj, JSExnType type,
-         ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName, HandleString stack,
+         ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName, HandleObject stack,
          uint32_t lineNumber, uint32_t columnNumber, HandleString message);
 
+    static bool checkAndUnwrapThis(JSContext *cx, CallArgs &args, const char *fnName,
+                                   MutableHandle<ErrorObject*> error);
+
   protected:
-    static const uint32_t EXNTYPE_SLOT      = 0;
-    static const uint32_t ERROR_REPORT_SLOT = EXNTYPE_SLOT + 1;
-    static const uint32_t FILENAME_SLOT     = ERROR_REPORT_SLOT + 1;
-    static const uint32_t LINENUMBER_SLOT   = FILENAME_SLOT + 1;
-    static const uint32_t COLUMNNUMBER_SLOT = LINENUMBER_SLOT + 1;
-    static const uint32_t STACK_SLOT        = COLUMNNUMBER_SLOT + 1;
-    static const uint32_t MESSAGE_SLOT      = STACK_SLOT + 1;
+    static const uint32_t EXNTYPE_SLOT          = 0;
+    static const uint32_t STACK_SLOT            = EXNTYPE_SLOT + 1;
+    static const uint32_t ERROR_REPORT_SLOT     = STACK_SLOT + 1;
+    static const uint32_t FILENAME_SLOT         = ERROR_REPORT_SLOT + 1;
+    static const uint32_t LINENUMBER_SLOT       = FILENAME_SLOT + 1;
+    static const uint32_t COLUMNNUMBER_SLOT     = LINENUMBER_SLOT + 1;
+    static const uint32_t MESSAGE_SLOT          = COLUMNNUMBER_SLOT + 1;
 
     static const uint32_t RESERVED_SLOTS = MESSAGE_SLOT + 1;
 
   public:
     static const Class classes[JSEXN_LIMIT];
 
     static const Class * classForType(JSExnType type) {
         MOZ_ASSERT(type != JSEXN_NONE);
@@ -63,17 +67,17 @@ class ErrorObject : public NativeObject
         return &classes[0] <= clasp && clasp < &classes[0] + mozilla::ArrayLength(classes);
     }
 
     // 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, HandleString stack, HandleString fileName,
+    create(JSContext *cx, JSExnType type, HandleObject stack, HandleString fileName,
            uint32_t lineNumber, uint32_t columnNumber, ScopedJSFreePtr<JSErrorReport> *report,
            HandleString message);
 
     /*
      * 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.)
      */
@@ -91,22 +95,27 @@ class ErrorObject : public NativeObject
         return static_cast<JSErrorReport*>(slot.toPrivate());
     }
 
     JSErrorReport * getOrCreateErrorReport(JSContext *cx);
 
     inline JSString * fileName(JSContext *cx) const;
     inline uint32_t lineNumber() const;
     inline uint32_t columnNumber() const;
-    inline JSString * stack(JSContext *cx) const;
+    inline JSObject * stack() const;
 
     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 setStack(JSContext *cx, unsigned argc, Value *vp);
+    static bool setStack_impl(JSContext *cx, CallArgs args);
 };
 
 } // namespace js
 
 template<>
 inline bool
 JSObject::is<js::ErrorObject>() const
 {
new file mode 100644
--- /dev/null
+++ b/js/src/vm/SavedStacks-inl.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_SavedStacksInl_h
+#define vm_SavedStacksInl_h
+
+#include "vm/SavedStacks.h"
+
+// Assert that if the given object is not null, it's Class is the
+// SavedFrame::class_ or the given object is a cross-compartment or Xray wrapper
+// around such an object.
+//
+// We allow wrappers here because the JSAPI functions for working with
+// SavedFrame objects and the SavedFrame accessors themselves handle wrappers
+// and use the original caller's compartment's principals to determine what
+// level of data to present. Unwrapping and entering the referent's compartment
+// would mess that up. See the module level documentation in
+// `js/src/vm/SavedStacks.h` as well as the comments in `js/src/jsapi.h`.
+inline void
+js::AssertObjectIsSavedFrameOrWrapper(JSContext *cx, HandleObject stack)
+{
+#ifdef DEBUG
+    if (stack) {
+        RootedObject savedFrameObj(cx, CheckedUnwrap(stack));
+        MOZ_ASSERT(savedFrameObj);
+        MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
+    }
+#endif
+}
+
+#endif // vm_SavedStacksInl_h
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -795,16 +795,21 @@ SavedStacks::init()
 }
 
 bool
 SavedStacks::saveCurrentStack(JSContext *cx, MutableHandleSavedFrame frame, unsigned maxFrameCount)
 {
     MOZ_ASSERT(initialized());
     assertSameCompartment(cx, this);
 
+    if (creatingSavedFrame) {
+        frame.set(nullptr);
+        return true;
+    }
+
     FrameIter iter(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED);
     return insertFrames(cx, iter, frame, maxFrameCount);
 }
 
 void
 SavedStacks::sweep(JSRuntime *rt)
 {
     if (frames.initialized()) {
@@ -915,18 +920,20 @@ SavedStacks::insertFrames(JSContext *cx,
         {
             AutoCompartment ac(cx, iter.compartment());
             if (!cx->compartment()->savedStacks().getLocation(cx, iter, &location))
                 return false;
         }
 
         // Use growByUninitialized and placement-new instead of just append.
         // We'd ideally like to use an emplace method once Vector supports it.
-        if (!stackChain->growByUninitialized(1))
+        if (!stackChain->growByUninitialized(1)) {
+            ReportOutOfMemory(cx);
             return false;
+        }
         new (&stackChain->back()) SavedFrame::Lookup(
           location->source,
           location->line,
           location->column,
           iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr,
           nullptr,
           nullptr,
           iter.compartment()->principals
@@ -987,18 +994,20 @@ SavedStacks::adoptAsyncStack(JSContext *
         maxFrameCount = ASYNC_STACK_MAX_FRAME_COUNT;
 
     // Accumulate the vector of Lookup objects in |stackChain|.
     SavedFrame::AutoLookupVector stackChain(cx);
     SavedFrame *currentSavedFrame = asyncStack;
     for (unsigned i = 0; i < maxFrameCount && currentSavedFrame; i++) {
         // Use growByUninitialized and placement-new instead of just append.
         // We'd ideally like to use an emplace method once Vector supports it.
-        if (!stackChain->growByUninitialized(1))
+        if (!stackChain->growByUninitialized(1)) {
+            ReportOutOfMemory(cx);
             return false;
+        }
         new (&stackChain->back()) SavedFrame::Lookup(*currentSavedFrame);
 
         // Attach the asyncCause to the youngest frame.
         if (i == 0)
             stackChain->back().asyncCause = asyncCauseAtom;
 
         currentSavedFrame = currentSavedFrame->getParent();
     }
@@ -1025,28 +1034,35 @@ SavedStacks::getOrCreateSavedFrame(JSCon
     DependentAddPtr<SavedFrame::Set> p(cx, frames, lookupInstance);
     if (p)
         return *p;
 
     RootedSavedFrame frame(cx, createFrameFromLookup(cx, lookup));
     if (!frame)
         return nullptr;
 
-    if (!p.add(cx, frames, lookupInstance, frame))
+    if (!p.add(cx, frames, lookupInstance, frame)) {
+        ReportOutOfMemory(cx);
         return nullptr;
+    }
 
     return frame;
 }
 
 SavedFrame *
 SavedStacks::createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup)
 {
     RootedGlobalObject global(cx, cx->global());
     assertSameCompartment(cx, global);
 
+    // Ensure that we don't try to capture the stack again in the
+    // `SavedStacksMetadataCallback` for this new SavedFrame object, and
+    // accidentally cause O(n^2) behavior.
+    SavedStacks::AutoReentrancyGuard guard(*this);
+
     RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global));
     if (!proto)
         return nullptr;
     assertSameCompartment(cx, proto);
 
     RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto));
     if (!frameObj)
         return nullptr;
@@ -1127,18 +1143,20 @@ SavedStacks::getLocation(JSContext *cx, 
         if (!source)
             return false;
 
         uint32_t column;
         uint32_t line = PCToLineNumber(script, pc, &column);
 
         // Make the column 1-based. See comment above.
         LocationValue value(source, line, column + 1);
-        if (!pcLocationMap.add(p, key, value))
+        if (!pcLocationMap.add(p, key, value)) {
+            ReportOutOfMemory(cx);
             return false;
+        }
     }
 
     locationp.set(p->value());
     return true;
 }
 
 void
 SavedStacks::chooseSamplingProbability(JSContext *cx)
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_SavedStacks_h
 #define vm_SavedStacks_h
 
 #include "jscntxt.h"
 #include "jsmath.h"
+#include "jswrapper.h"
 #include "js/HashTable.h"
 #include "vm/Stack.h"
 
 namespace js {
 
 class SavedFrame;
 typedef JS::Handle<SavedFrame*> HandleSavedFrame;
 typedef JS::MutableHandle<SavedFrame*> MutableHandleSavedFrame;
@@ -119,43 +120,69 @@ struct SavedFrame::HashPolicy
 
     static HashNumber hash(const Lookup &lookup);
     static bool       match(SavedFrame *existing, const Lookup &lookup);
 
     typedef ReadBarriered<SavedFrame*> Key;
     static void rekey(Key &key, const Key &newKey);
 };
 
+// Assert that if the given object is not null, that it must be either a
+// SavedFrame object or wrapper (Xray or CCW) around a SavedFrame object.
+inline void AssertObjectIsSavedFrameOrWrapper(JSContext *cx, HandleObject stack);
+
 class SavedStacks {
     friend JSObject *SavedStacksMetadataCallback(JSContext *cx);
 
   public:
     SavedStacks()
       : frames(),
         allocationSamplingProbability(1.0),
         allocationSkipCount(0),
-        rngState(0)
+        rngState(0),
+        creatingSavedFrame(false)
     { }
 
     bool     init();
     bool     initialized() const { return frames.initialized(); }
     bool     saveCurrentStack(JSContext *cx, MutableHandleSavedFrame frame, unsigned maxFrameCount = 0);
     void     sweep(JSRuntime *rt);
     void     trace(JSTracer *trc);
     uint32_t count();
     void     clear();
     void     setRNGState(uint64_t state) { rngState = state; }
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
   private:
-    SavedFrame::Set     frames;
-    double              allocationSamplingProbability;
-    uint32_t            allocationSkipCount;
-    uint64_t            rngState;
+    SavedFrame::Set frames;
+    double          allocationSamplingProbability;
+    uint32_t        allocationSkipCount;
+    uint64_t        rngState;
+    bool            creatingSavedFrame;
+
+    // Similar to mozilla::ReentrancyGuard, but instead of asserting against
+    // reentrancy, just change the behavior of SavedStacks::saveCurrentStack to
+    // return a nullptr SavedFrame.
+    struct MOZ_STACK_CLASS AutoReentrancyGuard {
+        MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+        SavedStacks &stacks;
+
+        explicit AutoReentrancyGuard(SavedStacks &stacks MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+            : stacks(stacks)
+        {
+            MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+            stacks.creatingSavedFrame = true;
+        }
+
+        ~AutoReentrancyGuard()
+        {
+            stacks.creatingSavedFrame = false;
+        }
+    };
 
     bool       insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
                             unsigned maxFrameCount = 0);
     bool       adoptAsyncStack(JSContext *cx, HandleSavedFrame asyncStack,
                                HandleString asyncCause,
                                MutableHandleSavedFrame adoptedStack,
                                unsigned maxFrameCount);
     SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -554,17 +554,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     }
   }
 
   function testErrorObjects() {
     // We only invoke testXray with Error, because that function isn't set up
     // to deal with dependent classes and fixing it up is more trouble than
     // it's worth.
     testXray('Error', new iwin.Error('some error message'), new iwin.Error(),
-             ['fileName', 'lineNumber', 'columnNumber', 'message', 'stack']);
+             ['fileName', 'lineNumber', 'columnNumber', 'message']);
 
     // Make sure that the dependent classes have their prototypes set up correctly.
     for (let c of errorObjectClasses.filter(x => x != "Error")) {
       var e = new iwin[c]('some message');
       is(Object.getPrototypeOf(e).name, c, "Prototype has correct name");
       is(Object.getPrototypeOf(Object.getPrototypeOf(e)), iwin.Error.prototype, "Dependent prototype set up correctly");
       is(e.name, c, "Exception name inherited correctly");
 
@@ -572,22 +572,29 @@ https://bugzilla.mozilla.org/show_bug.cg
         ok(criterion(e[name]), name + " property is correct: " + e[name]);
         e.wrappedJSObject[name] = goodReplacement;
         is(e[name], goodReplacement, name + " property ok after replacement: " + goodReplacement);
         e.wrappedJSObject[name] = faultyReplacement;
         is(e[name], undefined, name + " property censored after suspicious replacement");
       }
       testProperty('message', x => x == 'some message', 'some other message', 42);
       testProperty('fileName', x => /xul/.test(x), 'otherFilename.html', new iwin.Object());
+      testProperty('columnNumber', x => x > 5 && x < 100, 99, 99.5);
+      testProperty('lineNumber', x => x > 100 && x < 10000, 50, 'foo');
+
       // Note - an Exception newed via Xrays is going to have an empty stack given the
       // current semantics and implementation. This tests the current behavior, but that
       // may change in bug 1036527 or similar.
-      testProperty('stack', x => /^\s*$/.test(x), 'some other stack', new iwin.WeakMap());
-      testProperty('columnNumber', x => x > 5 && x < 100, 99, 99.5);
-      testProperty('lineNumber', x => x > 100 && x < 10000, 50, 'foo');
+      //
+      // Furthermore, xrays should always return an error's original stack, and
+      // not overwrite it.
+      var stack = e.stack;
+      ok(/^\s*$/.test(stack), "stack property should be correct");
+      e.wrappedJSObject.stack = "not a stack";
+      is(e.stack, stack, "Xrays should never get an overwritten stack property.");
     }
   }
 
   function testRegExp() {
     testXray('RegExp', new iwin.RegExp('foo'), new iwin.RegExp());
 
     // Test the self-hosted |flags| property, toString, and toSource.
     for (var flags of ["", "g", "i", "m", "y", "gimy"]) {
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -394,29 +394,29 @@ JSXrayTraits::resolveOwnProperty(JSConte
                     if (!JS_WrapObject(cx, &standardProto))
                         return false;
                     FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
                                            ObjectValue(*standardProto));
                     return true;
                 }
             }
         } else if (IsErrorObjectKey(key)) {
-            // The useful state of error objects is (unfortunately) represented
-            // as own data properties per-spec. This means that we can't have a
-            // a clean representation of the data (free from tampering) without
-            // doubling the slots of Error objects, which isn't great. So we
-            // forward these properties to the underlying object and then just
-            // censor any values with the wrong type. This limits the ability
-            // of content to do anything all that confusing.
+            // The useful state of error objects (except for .stack) is
+            // (unfortunately) represented as own data properties per-spec. This
+            // means that we can't have a a clean representation of the data
+            // (free from tampering) without doubling the slots of Error
+            // objects, which isn't great. So we forward these properties to the
+            // underlying object and then just censor any values with the wrong
+            // type. This limits the ability of content to do anything all that
+            // confusing.
             bool isErrorIntProperty =
                 id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_LINENUMBER) ||
                 id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_COLUMNNUMBER);
             bool isErrorStringProperty =
                 id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_FILENAME) ||
-                id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_STACK) ||
                 id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_MESSAGE);
             if (isErrorIntProperty || isErrorStringProperty) {
                 RootedObject waiver(cx, wrapper);
                 if (!WrapperFactory::WaiveXrayAndWrap(cx, &waiver))
                     return false;
                 if (!JS_GetOwnPropertyDescriptorById(cx, waiver, id, desc))
                     return false;
                 bool valueMatchesType = (isErrorIntProperty && desc.value().isInt32()) ||
--- a/toolkit/devtools/server/tests/unit/test_objectgrips-11.js
+++ b/toolkit/devtools/server/tests/unit/test_objectgrips-11.js
@@ -28,23 +28,22 @@ function run_test()
 function test_object_grip()
 {
   gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
     let args = aPacket.frame.arguments;
 
     let objClient = gThreadClient.pauseGrip(args[0]);
     objClient.getOwnPropertyNames(function(aResponse) {
       var opn = aResponse.ownPropertyNames;
-      do_check_eq(opn.length, 5);
+      do_check_eq(opn.length, 4);
       opn.sort();
       do_check_eq(opn[0], "columnNumber");
       do_check_eq(opn[1], "fileName");
       do_check_eq(opn[2], "lineNumber");
       do_check_eq(opn[3], "message");
-      do_check_eq(opn[4], "stack");
 
       gThreadClient.resume(function() {
         finishClient(gClient);
       });
     });
 
   });