Bug 724768 - Store the various properties of Error objects in reserved slots, and eagerly create the stack string for them. r=bhackett for the TI bits, r=jimb otherwise
authorJeff Walden <jwalden@mit.edu>
Mon, 17 Dec 2012 14:53:35 -0500
changeset 174005 54eac2d5c0392d2d8138e761a394ecf8e45407b4
parent 174004 7229b03abd0f4a72c4e32fcc61018412c1f64d8f
child 174006 479975fcd7368b78f47a2c8539a67702972af640
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett, jimb
bugs724768
milestone28.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 724768 - Store the various properties of Error objects in reserved slots, and eagerly create the stack string for them. r=bhackett for the TI bits, r=jimb otherwise
js/src/jit-test/tests/basic/metadata-hook.js
js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js
js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js
js/src/jsexn.cpp
js/src/jsexn.h
js/src/jsinfer.cpp
js/src/jswrapper.cpp
js/src/moz.build
js/src/vm/ErrorObject.cpp
js/src/vm/ErrorObject.h
js/src/vm/GlobalObject.h
toolkit/devtools/server/actors/script.js
toolkit/devtools/server/tests/unit/test_objectgrips-11.js
--- a/js/src/jit-test/tests/basic/metadata-hook.js
+++ b/js/src/jit-test/tests/basic/metadata-hook.js
@@ -5,17 +5,22 @@ assertEq(getObjectMetadata(x).y, 0);
 
 incallback = false;
 count = 0;
 
 function callback(obj) {
     if (incallback)
 	return null;
     incallback = true;
-    var res = {count:++count, location:Error().stack};
+    var res =
+      {
+        count: ++count,
+        location: Error().stack,
+        message: Error().message // no .message => Error.prototype.message => ""
+      };
     incallback = false;
     return res;
 }
 callback({});
 
 setObjectMetadataCallback(callback);
 
 function Foo() {
@@ -35,8 +40,9 @@ var wc = getObjectMetadata(w).count;
 var xc = getObjectMetadata(x).count;
 var yc = getObjectMetadata(y).count;
 var zc = getObjectMetadata(z).count;
 
 assertEq(xc > wc, true);
 assertEq(yc > xc, true);
 assertEq(zc > yc, true);
 assertEq(/\.js/.test(getObjectMetadata(x).location), true);
+assertEq(getObjectMetadata(x).message, "");
--- a/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-01.js
@@ -6,11 +6,11 @@ dbg.onDebuggerStatement = function (fram
     try {
         frame.arguments[0].deleteProperty("x");
     } catch (exc) {
         return;
     }
     throw new Error("deleteProperty should throw");
 };
 
-g.eval("function h(x) { debugger; }");
+g.eval("function h(obj) { debugger; }");
 g.eval("h(Proxy.create({delete: function () { throw Error.prototype; }}));");
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-deleteProperty-error-02.js
@@ -0,0 +1,27 @@
+var g = newGlobal();
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+    try {
+        frame.arguments[0].deleteProperty("x");
+    } catch (exc) {
+        assertEq(exc instanceof ReferenceError, true);
+        assertEq(exc.message, "diaf");
+        assertEq(exc.fileName, "fail");
+        assertEq(exc.lineNumber, 4);
+
+        // Arrant nonsense?  Sure -- but different from lineNumber is all this
+        // test exists to verify.  If you're the person to make column numbers
+        // actually work, change this accordingly.
+        assertEq(exc.columnNumber, 0);
+        return;
+    }
+    throw new Error("deleteProperty should throw");
+};
+
+g.evaluate("function h(obj) { debugger; } \n" +
+           "h(new Proxy({}, \n" +
+           "            { deleteProperty: function () { \n" +
+           "                var e = new ReferenceError('diaf', 'fail'); \n" +
+           "                throw e; \n" +
+           "              } \n" +
+           "            }));");
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -35,76 +35,38 @@
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
 
 using mozilla::ArrayLength;
 using mozilla::PodArrayZero;
 using mozilla::PodZero;
 
-/* Forward declarations for ErrorObject::class_'s initializer. */
-static bool
-Exception(JSContext *cx, unsigned argc, Value *vp);
-
-static void
-exn_trace(JSTracer *trc, JSObject *obj);
-
 static void
 exn_finalize(FreeOp *fop, JSObject *obj);
 
-static bool
-exn_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
-            MutableHandleObject objp);
-
 const Class ErrorObject::class_ = {
     js_Error_str,
-    JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_NEW_RESOLVE |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Error),
+    JSCLASS_IMPLEMENTS_BARRIERS |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Error) |
+    JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS),
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
-    (JSResolveOp)exn_resolve,
+    JS_ResolveStub,
     JS_ConvertStub,
     exn_finalize,
     nullptr,                 /* checkAccess */
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
-    nullptr,                 /* construct   */
-    exn_trace
-};
-
-template <typename T>
-struct JSStackTraceElemImpl
-{
-    T                   funName;
-    const char          *filename;
-    unsigned            ulineno;
+    nullptr                  /* construct   */
 };
 
-typedef JSStackTraceElemImpl<HeapPtrString> JSStackTraceElem;
-typedef JSStackTraceElemImpl<JSString *>    JSStackTraceStackElem;
-
-struct JSExnPrivate
-{
-    /* A copy of the JSErrorReport originally generated. */
-    JSErrorReport       *errorReport;
-    js::HeapPtrString   message;
-    js::HeapPtrString   filename;
-    unsigned            lineno;
-    unsigned            column;
-    size_t              stackDepth;
-    int                 exnType;
-    JSStackTraceElem    stackElems[1];
-};
-
-static JSString *
-StackTraceToString(JSContext *cx, JSExnPrivate *priv);
-
 static JSErrorReport *
 CopyErrorReport(JSContext *cx, JSErrorReport *report)
 {
     /*
      * We use a single malloc block to make a deep copy of JSErrorReport with
      * the following layout:
      *   JSErrorReport
      *   array of copies of report->messageArgs
@@ -237,368 +199,161 @@ struct SuppressErrorsGuard
 
     ~SuppressErrorsGuard()
     {
         JS_RestoreExceptionState(cx, prevState);
         JS_SetErrorReporter(cx, prevReporter);
     }
 };
 
-static void
-SetExnPrivate(ErrorObject &exnObject, JSExnPrivate *priv);
-
-static bool
-InitExnPrivate(JSContext *cx, HandleObject exnObject, HandleString message,
-               HandleString filename, unsigned lineno, unsigned column,
-               JSErrorReport *report, int exnType)
+static JSString *
+ComputeStackString(JSContext *cx)
 {
-    JS_ASSERT(exnObject->is<ErrorObject>());
-    JS_ASSERT(!exnObject->getPrivate());
-
     JSCheckAccessOp checkAccess = cx->runtime()->securityCallbacks->checkObjectAccess;
 
-    Vector<JSStackTraceStackElem> frames(cx);
+    StringBuffer sb(cx);
+
     {
+        RootedAtom atom(cx);
         SuppressErrorsGuard seg(cx);
         for (NonBuiltinScriptFrameIter i(cx); !i.done(); ++i) {
-
-            /* Ask the crystal CAPS ball whether we can see across compartments. */
+            // Cut off the stack if this callee crosses a trust boundary.
             if (checkAccess && i.isNonEvalFunctionFrame()) {
                 RootedValue v(cx);
                 RootedId callerid(cx, NameToId(cx->names().caller));
                 RootedObject obj(cx, i.callee());
                 if (!checkAccess(cx, obj, callerid, JSACC_READ, &v))
                     break;
             }
 
-            if (!frames.growBy(1))
-                return false;
-            JSStackTraceStackElem &frame = frames.back();
-            if (i.isNonEvalFunctionFrame()) {
-                JSAtom *atom = i.callee()->displayAtom();
-                if (atom == nullptr)
-                    atom = cx->runtime()->emptyString;
-                frame.funName = atom;
-            } else {
-                frame.funName = nullptr;
-            }
+            /* First append the function name, if any. */
+            atom = nullptr;
+            if (i.isNonEvalFunctionFrame() && i.callee()->displayAtom())
+                atom = i.callee()->displayAtom();
+            if (atom && !sb.append(atom))
+                return nullptr;
+
+            /* Next a @ separating function name from source location. */
+            if (!sb.append('@'))
+                return nullptr;
+
+            /* Now the filename. */
             RootedScript script(cx, i.script());
             const char *cfilename = script->filename();
             if (!cfilename)
                 cfilename = "";
-            frame.filename = cfilename;
-            frame.ulineno = PCToLineNumber(script, i.pc());
+            if (!sb.appendInflated(cfilename, strlen(cfilename)))
+                return nullptr;
+
+            /* Finally, : followed by the line number and a newline. */
+            uint32_t line = PCToLineNumber(script, i.pc());
+            if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(line), sb) ||
+                !sb.append('\n'))
+            {
+                return nullptr;
+            }
+
+            /*
+             * Cut off the stack if it gets too deep (most commonly for
+             * infinite recursion errors).
+             */
+            const size_t MaxReportedStackDepth = 1u << 20;
+            if (sb.length() > MaxReportedStackDepth)
+                break;
         }
     }
 
-    /* Do not need overflow check: the vm stack is already bigger. */
-    JS_STATIC_ASSERT(sizeof(JSStackTraceElem) <= sizeof(StackFrame));
-
-    size_t nbytes = offsetof(JSExnPrivate, stackElems) +
-                    frames.length() * sizeof(JSStackTraceElem);
-
-    JSExnPrivate *priv = (JSExnPrivate *)cx->malloc_(nbytes);
-    if (!priv)
-        return false;
-
-    /* Initialize to zero so that write barriers don't witness undefined values. */
-    memset(priv, 0, nbytes);
-
-    if (report) {
-        /*
-         * Construct a new copy of the error report struct. We can't use the
-         * error report struct that was passed in, because it's allocated on
-         * the stack, and also because it may point to transient data in the
-         * TokenStream.
-         */
-        priv->errorReport = CopyErrorReport(cx, report);
-        if (!priv->errorReport) {
-            js_free(priv);
-            return false;
-        }
-    } else {
-        priv->errorReport = nullptr;
-    }
-
-    priv->message.init(message);
-    priv->filename.init(filename);
-    priv->lineno = lineno;
-    priv->column = column;
-    priv->stackDepth = frames.length();
-    priv->exnType = exnType;
-    for (size_t i = 0; i < frames.length(); ++i) {
-        priv->stackElems[i].funName.init(frames[i].funName);
-        priv->stackElems[i].filename = JS_strdup(cx, frames[i].filename);
-        if (!priv->stackElems[i].filename)
-            return false;
-        priv->stackElems[i].ulineno = frames[i].ulineno;
-    }
-
-    SetExnPrivate(exnObject->as<ErrorObject>(), priv);
-    return true;
-}
-
-static void
-exn_trace(JSTracer *trc, JSObject *obj)
-{
-    if (JSExnPrivate *priv = obj->as<ErrorObject>().getExnPrivate()) {
-        if (priv->message)
-            MarkString(trc, &priv->message, "exception message");
-        if (priv->filename)
-            MarkString(trc, &priv->filename, "exception filename");
-
-        for (size_t i = 0; i != priv->stackDepth; ++i) {
-            JSStackTraceElem &elem = priv->stackElems[i];
-            if (elem.funName)
-                MarkString(trc, &elem.funName, "stack trace function name");
-        }
-    }
-}
-
-/* NB: An error object's private must be set through this function. */
-static void
-SetExnPrivate(ErrorObject &exnObject, JSExnPrivate *priv)
-{
-    JS_ASSERT(!exnObject.getExnPrivate());
-    if (JSErrorReport *report = priv->errorReport) {
-        if (JSPrincipals *prin = report->originPrincipals)
-            JS_HoldPrincipals(prin);
-    }
-    exnObject.setPrivate(priv);
+    return sb.finishString();
 }
 
 static void
 exn_finalize(FreeOp *fop, JSObject *obj)
 {
-    if (JSExnPrivate *priv = obj->as<ErrorObject>().getExnPrivate()) {
-        if (JSErrorReport *report = priv->errorReport) {
-            /* HOLD called by SetExnPrivate. */
-            if (JSPrincipals *prin = report->originPrincipals)
-                JS_DropPrincipals(fop->runtime(), prin);
-            fop->free_(report);
-        }
-        for (size_t i = 0; i < priv->stackDepth; i++)
-            js_free(const_cast<char *>(priv->stackElems[i].filename));
-        fop->free_(priv);
+    if (JSErrorReport *report = obj->as<ErrorObject>().getErrorReport()) {
+        /* These were held by ErrorObject::init. */
+        if (JSPrincipals *prin = report->originPrincipals)
+            JS_DropPrincipals(fop->runtime(), prin);
+        fop->free_(report);
     }
 }
 
-static bool
-exn_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
-            MutableHandleObject objp)
-{
-    JSExnPrivate *priv;
-    const char *prop;
-    jsval v;
-    unsigned attrs;
-
-    objp.set(nullptr);
-    priv = obj->as<ErrorObject>().getExnPrivate();
-    if (priv && JSID_IS_ATOM(id)) {
-        RootedString str(cx, JSID_TO_STRING(id));
-
-        RootedAtom atom(cx, cx->names().message);
-        if (str == atom) {
-            prop = js_message_str;
-
-            /*
-             * Per ES5 15.11.1.1, if Error is called with no argument or with
-             * undefined as the argument, it returns an Error object with no
-             * own message property.
-             */
-            if (!priv->message)
-                return true;
-
-            v = STRING_TO_JSVAL(priv->message);
-            attrs = 0;
-            goto define;
-        }
-
-        atom = cx->names().fileName;
-        if (str == atom) {
-            prop = js_fileName_str;
-            v = STRING_TO_JSVAL(priv->filename);
-            attrs = JSPROP_ENUMERATE;
-            goto define;
-        }
-
-        atom = cx->names().lineNumber;
-        if (str == atom) {
-            prop = js_lineNumber_str;
-            v = UINT_TO_JSVAL(priv->lineno);
-            attrs = JSPROP_ENUMERATE;
-            goto define;
-        }
-
-        atom = cx->names().columnNumber;
-        if (str == atom) {
-            prop = js_columnNumber_str;
-            v = UINT_TO_JSVAL(priv->column);
-            attrs = JSPROP_ENUMERATE;
-            goto define;
-        }
-
-        atom = cx->names().stack;
-        if (str == atom) {
-            JSString *stack = StackTraceToString(cx, priv);
-            if (!stack)
-                return false;
-
-            prop = js_stack_str;
-            v = STRING_TO_JSVAL(stack);
-            attrs = JSPROP_ENUMERATE;
-            goto define;
-        }
-    }
-    return true;
-
-  define:
-    if (!JS_DefineProperty(cx, obj, prop, v, nullptr, nullptr, attrs))
-        return false;
-    objp.set(obj);
-    return true;
-}
-
 JSErrorReport *
 js_ErrorFromException(jsval exn)
 {
     if (JSVAL_IS_PRIMITIVE(exn))
         return nullptr;
 
     // It's ok to UncheckedUnwrap here, since all we do is get the
     // JSErrorReport, and consumers are careful with the information they get
     // from that anyway.  Anyone doing things that would expose anything in the
     // JSErrorReport to page script either does a security check on the
     // JSErrorReport's principal or also tries to do toString on our object and
     // will fail if they can't unwrap it.
     JSObject *obj = UncheckedUnwrap(JSVAL_TO_OBJECT(exn));
     if (!obj->is<ErrorObject>())
         return nullptr;
 
-    JSExnPrivate *priv = obj->as<ErrorObject>().getExnPrivate();
-    if (!priv)
-        return nullptr;
-
-    return priv->errorReport;
-}
-
-static JSString *
-StackTraceToString(JSContext *cx, JSExnPrivate *priv)
-{
-    StringBuffer sb(cx);
-
-    JSStackTraceElem *element = priv->stackElems, *end = element + priv->stackDepth;
-    for (; element < end; element++) {
-        /* Try to reserve required space upfront, so we don't fail inbetween. */
-        size_t length = ((element->funName ? element->funName->length() : 0) +
-                         (element->filename ? strlen(element->filename) * 2 : 0) +
-                         13); /* "@" + ":" + "4294967295" + "\n" */
-
-        if (!sb.reserve(length) || sb.length() > JS_BIT(20))
-            break; /* Return as much as we got. */
-
-        if (element->funName) {
-            if (!sb.append(element->funName))
-                return nullptr;
-        }
-        if (!sb.append('@'))
-            return nullptr;
-        if (element->filename) {
-            if (!sb.appendInflated(element->filename, strlen(element->filename)))
-                return nullptr;
-        }
-        if (!sb.append(':') || !NumberValueToStringBuffer(cx, NumberValue(element->ulineno), sb) || 
-            !sb.append('\n'))
-        {
-            return nullptr;
-        }
-    }
-
-    return sb.finishString();
-}
-
-/* XXXbe Consolidate the ugly truth that we don't treat filename as UTF-8
-         with these two functions. */
-static JSString *
-FilenameToString(JSContext *cx, const char *filename)
-{
-    return JS_NewStringCopyZ(cx, filename);
+    return obj->as<ErrorObject>().getErrorReport();
 }
 
 static bool
-Exception(JSContext *cx, unsigned argc, Value *vp)
+Error(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    /*
-     * 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, whose .name member is used by
-     * NewNativeClassInstance to find the class prototype, we must get the
-     * class prototype ourselves.
-     */
-    RootedObject callee(cx, &args.callee());
-    RootedValue protov(cx);
-    if (!JSObject::getProperty(cx, callee, callee, cx->names().prototype, &protov))
-        return false;
-
-    if (!protov.isObject()) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_PROTOTYPE, "Error");
-        return false;
-    }
-
-    RootedObject obj(cx, NewObjectWithGivenProto(cx, &ErrorObject::class_, &protov.toObject(),
-                     nullptr));
-    if (!obj)
-        return false;
-
-    /* Set the 'message' property. */
-    RootedString message(cx);
+    /* Compute the error message, if any. */
+    RootedString message(cx, nullptr);
     if (args.hasDefined(0)) {
         message = ToString<CanGC>(cx, args[0]);
         if (!message)
             return false;
-        args[0].setString(message);
-    } else {
-        message = nullptr;
     }
 
     /* Find the scripted caller. */
     NonBuiltinScriptFrameIter iter(cx);
 
     /* Set the 'fileName' property. */
     RootedScript script(cx, iter.done() ? nullptr : iter.script());
-    RootedString filename(cx);
+    RootedString fileName(cx);
     if (args.length() > 1) {
-        filename = ToString<CanGC>(cx, args[1]);
-        if (!filename)
-            return false;
-        args[1].setString(filename);
+        fileName = ToString<CanGC>(cx, args[1]);
     } else {
-        filename = cx->runtime()->emptyString;
+        fileName = cx->runtime()->emptyString;
         if (!iter.done()) {
-            if (const char *cfilename = script->filename()) {
-                filename = FilenameToString(cx, cfilename);
-                if (!filename)
-                    return false;
-            }
+            if (const char *cfilename = script->filename())
+                fileName = JS_NewStringCopyZ(cx, cfilename);
         }
     }
+    if (!fileName)
+        return false;
 
     /* Set the 'lineNumber' property. */
-    uint32_t lineno, column = 0;
+    uint32_t lineNumber, columnNumber = 0;
     if (args.length() > 2) {
-        if (!ToUint32(cx, args[2], &lineno))
+        if (!ToUint32(cx, args[2], &lineNumber))
             return false;
     } else {
-        lineno = iter.done() ? 0 : PCToLineNumber(script, iter.pc(), &column);
+        lineNumber = iter.done() ? 0 : PCToLineNumber(script, iter.pc(), &columnNumber);
     }
 
-    int exnType = args.callee().as<JSFunction>().getExtendedSlot(0).toInt32();
-    if (!InitExnPrivate(cx, obj, message, filename, lineno, column, nullptr, exnType))
+    Rooted<JSString*> stack(cx, ComputeStackString(cx));
+    if (!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.
+     */
+    JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
+
+    RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
+                                             lineNumber, columnNumber, nullptr, message));
+    if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 /* ES5 15.11.4.4 (NB: with subsequent errata). */
 static bool
@@ -769,90 +524,81 @@ JS_STATIC_ASSERT(JSEXN_ERR == 0);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_INTERNALERR  == JSProto_InternalError);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_EVALERR      == JSProto_EvalError);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_RANGEERR     == JSProto_RangeError);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_REFERENCEERR == JSProto_ReferenceError);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_SYNTAXERR    == JSProto_SyntaxError);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_TYPEERR      == JSProto_TypeError);
 JS_STATIC_ASSERT(JSProto_Error + JSEXN_URIERR       == JSProto_URIError);
 
-static JSObject *
-InitErrorClass(JSContext *cx, Handle<GlobalObject*> global, int type, HandleObject proto)
+/* static */ ErrorObject *
+ErrorObject::createProto(JSContext *cx, JS::Handle<GlobalObject*> global, JSExnType type,
+                         JS::HandleObject proto)
 {
-    JSProtoKey key = GetExceptionProtoKey(type);
-    RootedAtom name(cx, ClassName(key, cx));
-    RootedObject errorProto(cx, global->createBlankPrototypeInheriting(cx, &ErrorObject::class_,
-                            *proto));
+    RootedObject errorProto(cx);
+    errorProto = global->createBlankPrototypeInheriting(cx, &ErrorObject::class_, *proto);
     if (!errorProto)
         return nullptr;
 
+    Rooted<ErrorObject*> err(cx, &errorProto->as<ErrorObject>());
+    RootedString emptyStr(cx, cx->names().empty);
+    if (!ErrorObject::init(cx, err, type, nullptr, emptyStr, emptyStr, 0, 0, emptyStr))
+        return nullptr;
+
+    // The various prototypes also have .name in addition to the normal error
+    // instance properties.
+    JSProtoKey key = GetExceptionProtoKey(type);
+    RootedPropertyName name(cx, ClassName(key, cx));
     RootedValue nameValue(cx, StringValue(name));
-    RootedValue zeroValue(cx, Int32Value(0));
-    RootedValue empty(cx, StringValue(cx->runtime()->emptyString));
-    RootedId nameId(cx, NameToId(cx->names().name));
-    RootedId messageId(cx, NameToId(cx->names().message));
-    RootedId fileNameId(cx, NameToId(cx->names().fileName));
-    RootedId lineNumberId(cx, NameToId(cx->names().lineNumber));
-    RootedId columnNumberId(cx, NameToId(cx->names().columnNumber));
-    if (!DefineNativeProperty(cx, errorProto, nameId, nameValue,
-                              JS_PropertyStub, JS_StrictPropertyStub, 0, 0, 0) ||
-        !DefineNativeProperty(cx, errorProto, messageId, empty,
-                              JS_PropertyStub, JS_StrictPropertyStub, 0, 0, 0) ||
-        !DefineNativeProperty(cx, errorProto, fileNameId, empty,
-                              JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0) ||
-        !DefineNativeProperty(cx, errorProto, lineNumberId, zeroValue,
-                              JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0) ||
-        !DefineNativeProperty(cx, errorProto, columnNumberId, zeroValue,
-                              JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE, 0, 0))
+    if (!JSObject::defineProperty(cx, err, cx->names().name, nameValue,
+                                  JS_PropertyStub, JS_StrictPropertyStub, 0))
     {
         return nullptr;
     }
 
-    /* Create the corresponding constructor. */
-    RootedFunction ctor(cx, global->createConstructor(cx, Exception, name, 1,
+    // Create the corresponding constructor.
+    RootedFunction ctor(cx, global->createConstructor(cx, Error, name, 1,
                                                       JSFunction::ExtendedFinalizeKind));
     if (!ctor)
         return nullptr;
     ctor->setExtendedSlot(0, Int32Value(int32_t(type)));
 
-    if (!LinkConstructorAndPrototype(cx, ctor, errorProto))
+    if (!LinkConstructorAndPrototype(cx, ctor, err))
         return nullptr;
 
-    if (!DefineConstructorAndPrototype(cx, global, key, ctor, errorProto))
+    if (!DefineConstructorAndPrototype(cx, global, key, ctor, err))
         return nullptr;
 
-    JS_ASSERT(!errorProto->getPrivate());
-
-    return errorProto;
+    return err;
 }
 
 JSObject *
 js_InitExceptionClasses(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->is<GlobalObject>());
     JS_ASSERT(obj->isNative());
 
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
 
-    RootedObject objectProto(cx, global->getOrCreateObjectPrototype(cx));
-    if (!objectProto)
+    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+    if (!objProto)
         return nullptr;
 
     /* Initialize the base Error class first. */
-    RootedObject errorProto(cx, InitErrorClass(cx, global, JSEXN_ERR, objectProto));
+    RootedObject errorProto(cx, ErrorObject::createProto(cx, global, JSEXN_ERR, objProto));
     if (!errorProto)
         return nullptr;
 
     /* |Error.prototype| alone has method properties. */
     if (!DefinePropertiesAndBrand(cx, errorProto, nullptr, exception_methods))
         return nullptr;
 
     /* Define all remaining *Error constructors. */
     for (int i = JSEXN_ERR + 1; i < JSEXN_LIMIT; i++) {
-        if (!InitErrorClass(cx, global, i, errorProto))
+        if (!ErrorObject::createProto(cx, global, JSExnType(i), errorProto))
             return nullptr;
     }
 
     return errorProto;
 }
 
 const JSErrorFormatString*
 js_GetLocalizedErrorMessage(ExclusiveContext *cx, void *userRef, const char *locale,
@@ -883,111 +629,80 @@ js::GetErrorTypeName(JSRuntime* rt, int1
      * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
      * is prepended before "uncaught exception: "
      */
     if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
         exnType == JSEXN_INTERNALERR)
     {
         return nullptr;
     }
-    JSProtoKey key = GetExceptionProtoKey(exnType);
+    JSProtoKey key = GetExceptionProtoKey(JSExnType(exnType));
     return ClassName(key, rt)->chars();
 }
 
-#if defined ( DEBUG_mccabe ) && defined ( PRINTNAMES )
-/* For use below... get character strings for error name and exception name */
-static const struct exnname { char *name; char *exception; } errortoexnname[] = {
-#define MSG_DEF(name, number, count, exception, format) \
-    {#name, #exception},
-#include "js.msg"
-#undef MSG_DEF
-};
-#endif /* DEBUG */
-
 bool
 js_ErrorToException(JSContext *cx, const char *message, JSErrorReport *reportp,
                     JSErrorCallback callback, void *userRef)
 {
-    JSErrNum errorNumber;
-    const JSErrorFormatString *errorString;
-    JSExnType exn;
-    jsval tv[4];
-
     /*
      * Tell our caller to report immediately if this report is just a warning.
      */
     JS_ASSERT(reportp);
     if (JSREPORT_IS_WARNING(reportp->flags))
         return false;
 
     /* Find the exception index associated with this error. */
-    errorNumber = (JSErrNum) reportp->errorNumber;
+    JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
+    const JSErrorFormatString *errorString;
     if (!callback || callback == js_GetErrorMessage)
         errorString = js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber);
     else
         errorString = callback(userRef, nullptr, errorNumber);
-    exn = errorString ? (JSExnType) errorString->exnType : JSEXN_NONE;
-    JS_ASSERT(exn < JSEXN_LIMIT);
-
-#if defined( DEBUG_mccabe ) && defined ( PRINTNAMES )
-    /* Print the error name and the associated exception name to stderr */
-    fprintf(stderr, "%s\t%s\n",
-            errortoexnname[errorNumber].name,
-            errortoexnname[errorNumber].exception);
-#endif
+    JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
+    MOZ_ASSERT(exnType < JSEXN_LIMIT);
 
     /*
      * Return false (no exception raised) if no exception is associated
      * with the given error number.
      */
-    if (exn == JSEXN_NONE)
+    if (exnType == JSEXN_NONE)
         return false;
 
     /* Prevent infinite recursion. */
     if (cx->generatingError)
         return false;
     AutoScopedAssign<bool> asa(&cx->generatingError, true);
 
-    /* Protect the newly-created strings below from nesting GCs. */
-    PodArrayZero(tv);
-    AutoArrayRooter tvr(cx, ArrayLength(tv), tv);
-
-    /*
-     * Try to get an appropriate prototype by looking up the corresponding
-     * exception constructor name in the scope chain of the current context's
-     * top stack frame, or in the global object if no frame is active.
-     */
-    RootedObject errProto(cx);
-    if (!js_GetClassPrototype(cx, GetExceptionProtoKey(exn), &errProto))
-        return false;
-    tv[0] = OBJECT_TO_JSVAL(errProto);
-
-    RootedObject errObject(cx, NewObjectWithGivenProto(cx, &ErrorObject::class_,
-                                                       errProto, nullptr));
-    if (!errObject)
-        return false;
-    tv[1] = OBJECT_TO_JSVAL(errObject);
-
     RootedString messageStr(cx, reportp->ucmessage ? JS_NewUCStringCopyZ(cx, reportp->ucmessage)
                                                    : JS_NewStringCopyZ(cx, message));
     if (!messageStr)
         return false;
-    tv[2] = STRING_TO_JSVAL(messageStr);
+
+    RootedString fileName(cx, JS_NewStringCopyZ(cx, reportp->filename));
+    if (!fileName)
+        return false;
 
-    RootedString filenameStr(cx, JS_NewStringCopyZ(cx, reportp->filename));
-    if (!filenameStr)
+    uint32_t lineNumber = reportp->lineno;
+    uint32_t columnNumber = reportp->column;
+
+    RootedString stack(cx, ComputeStackString(cx));
+    if (!stack)
         return false;
-    tv[3] = STRING_TO_JSVAL(filenameStr);
 
-    if (!InitExnPrivate(cx, errObject, messageStr, filenameStr,
-                        reportp->lineno, reportp->column, reportp, exn)) {
+    js::ScopedJSFreePtr<JSErrorReport> report(CopyErrorReport(cx, reportp));
+    if (!report)
         return false;
-    }
 
-    RootedValue errValue(cx, OBJECT_TO_JSVAL(errObject));
+    RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName,
+                                                   lineNumber, columnNumber, &report,
+                                                   messageStr));
+    if (!errObject)
+        return false;
+
+    RootedValue errValue(cx, ObjectValue(*errObject));
     JS_SetPendingException(cx, errValue);
 
     /* Flag the error report passed in to indicate an exception was raised. */
     reportp->flags |= JSREPORT_EXCEPTION;
     return true;
 }
 
 static bool
@@ -1010,18 +725,16 @@ IsDuckTypedErrorObject(JSContext *cx, Ha
 
     *filename_strp = filename_str;
     return true;
 }
 
 bool
 js_ReportUncaughtException(JSContext *cx)
 {
-    JSErrorReport *reportp, report;
-
     if (!cx->isExceptionPending())
         return true;
 
     RootedValue exn(cx, cx->getPendingException());
     AutoValueVector roots(cx);
     roots.resize(6);
 
     /*
@@ -1034,23 +747,25 @@ js_ReportUncaughtException(JSContext *cx
     if (JSVAL_IS_PRIMITIVE(exn)) {
         exnObject = nullptr;
     } else {
         exnObject = JSVAL_TO_OBJECT(exn);
         roots[0] = exn;
     }
 
     JS_ClearPendingException(cx);
-    reportp = js_ErrorFromException(exn);
+    JSErrorReport *reportp = js_ErrorFromException(exn);
 
     /* XXX L10N angels cry once again. see also everywhere else */
     RootedString str(cx, ToString<CanGC>(cx, exn));
     if (str)
         roots[1] = StringValue(str);
 
+    JSErrorReport report;
+
     const char *filename_str = js_fileName_str;
     JSAutoByteString filename;
     if (!reportp && exnObject &&
         (exnObject->is<ErrorObject>() || IsDuckTypedErrorObject(cx, exnObject, &filename_str)))
     {
         RootedString name(cx);
         if (JS_GetProperty(cx, exnObject, js_name_str, roots.handleAt(2)) && roots[2].isString())
             name = roots[2].toString();
@@ -1125,55 +840,37 @@ js_ReportUncaughtException(JSContext *cx
         JS_SetPendingException(cx, exn);
         js_ReportErrorAgain(cx, bytes, reportp);
     }
 
     JS_ClearPendingException(cx);
     return true;
 }
 
-extern JSObject *
-js_CopyErrorObject(JSContext *cx, HandleObject errobj, HandleObject scope)
+JSObject *
+js_CopyErrorObject(JSContext *cx, Handle<ErrorObject*> err, HandleObject scope)
 {
     assertSameCompartment(cx, scope);
-    JSExnPrivate *priv = errobj->as<ErrorObject>().getExnPrivate();
 
-    size_t size = offsetof(JSExnPrivate, stackElems) +
-                  priv->stackDepth * sizeof(JSStackTraceElem);
-
-    ScopedJSFreePtr<JSExnPrivate> copy(static_cast<JSExnPrivate *>(cx->malloc_(size)));
-    if (!copy)
-        return nullptr;
-
-    if (priv->errorReport) {
-        copy->errorReport = CopyErrorReport(cx, priv->errorReport);
-        if (!copy->errorReport)
+    js::ScopedJSFreePtr<JSErrorReport> copyReport;
+    if (JSErrorReport *errorReport = err->getErrorReport()) {
+        copyReport = CopyErrorReport(cx, errorReport);
+        if (!copyReport)
             return nullptr;
-    } else {
-        copy->errorReport = nullptr;
     }
-    ScopedJSFreePtr<JSErrorReport> autoFreeErrorReport(copy->errorReport);
 
-    copy->message.init(priv->message);
-    if (!cx->compartment()->wrap(cx, &copy->message))
+    RootedString message(cx, err->getMessage());
+    if (message && !cx->compartment()->wrap(cx, message.address()))
         return nullptr;
-    JS::Anchor<JSString *> messageAnchor(copy->message);
-    copy->filename.init(priv->filename);
-    if (!cx->compartment()->wrap(cx, &copy->filename))
+    RootedString fileName(cx, err->fileName());
+    if (!cx->compartment()->wrap(cx, fileName.address()))
         return nullptr;
-    JS::Anchor<JSString *> filenameAnchor(copy->filename);
-    copy->lineno = priv->lineno;
-    copy->column = priv->column;
-    copy->stackDepth = 0;
-    copy->exnType = priv->exnType;
+    RootedString stack(cx, err->stack());
+    if (!cx->compartment()->wrap(cx, stack.address()))
+        return nullptr;
+    uint32_t lineNumber = err->lineNumber();
+    uint32_t columnNumber = err->columnNumber();
+    JSExnType errorType = err->type();
 
     // Create the Error object.
-    RootedObject proto(cx, scope->global().getOrCreateCustomErrorPrototype(cx, copy->exnType));
-    if (!proto)
-        return nullptr;
-    RootedObject copyobj(cx, NewObjectWithGivenProto(cx, &ErrorObject::class_, proto, nullptr));
-    if (!copyobj)
-        return nullptr;
-    SetExnPrivate(copyobj->as<ErrorObject>(), copy);
-    copy.forget();
-    autoFreeErrorReport.forget();
-    return copyobj;
+    return ErrorObject::create(cx, errorType, stack, fileName,
+                               lineNumber, columnNumber, &copyReport, message);
 }
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -9,21 +9,19 @@
  */
 
 #ifndef jsexn_h
 #define jsexn_h
 
 #include "jsapi.h"
 #include "NamespaceImports.h"
 
-/*
- * Initialize the exception constructor/prototype hierarchy.
- */
-extern JSObject *
-js_InitExceptionClasses(JSContext *cx, js::HandleObject obj);
+namespace js {
+class ErrorObject;
+}
 
 /*
  * Given a JSErrorReport, check to see if there is an exception associated with
  * the error number.  If there is, then create an appropriate exception object,
  * set it as the pending exception, and set the JSREPORT_EXCEPTION flag on the
  * error report.  Exception-aware host error reporters should probably ignore
  * error reports so flagged.  Returns true if an associated exception is
  * found and set, false otherwise.
@@ -62,19 +60,19 @@ js_GetLocalizedErrorMessage(js::Exclusiv
  * Make a copy of errobj parented to scope.
  *
  * cx must be in the same compartment as scope. errobj may be in a different
  * compartment, but it must be an Error object (not a wrapper of one) and it
  * must not be one of the prototype objects created by js_InitExceptionClasses
  * (errobj->getPrivate() must not be nullptr).
  */
 extern JSObject *
-js_CopyErrorObject(JSContext *cx, js::HandleObject errobj, js::HandleObject scope);
+js_CopyErrorObject(JSContext *cx, JS::Handle<js::ErrorObject*> errobj, js::HandleObject scope);
 
 static inline JSProtoKey
-GetExceptionProtoKey(int exn)
+GetExceptionProtoKey(JSExnType exn)
 {
     JS_ASSERT(JSEXN_ERR <= exn);
     JS_ASSERT(exn < JSEXN_LIMIT);
-    return JSProtoKey(JSProto_Error + exn);
+    return JSProtoKey(JSProto_Error + int(exn));
 }
 
 #endif /* jsexn_h */
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3843,16 +3843,23 @@ ExclusiveContext::getNewType(const Class
             AddTypeProperty(this, type, "ignoreCase", types::Type::BooleanType());
             AddTypeProperty(this, type, "multiline", types::Type::BooleanType());
             AddTypeProperty(this, type, "sticky", types::Type::BooleanType());
             AddTypeProperty(this, type, "lastIndex", types::Type::Int32Type());
         }
 
         if (obj->is<StringObject>())
             AddTypeProperty(this, type, "length", Type::Int32Type());
+
+        if (obj->is<ErrorObject>()) {
+            AddTypeProperty(this, type, "fileName", types::Type::StringType());
+            AddTypeProperty(this, type, "lineNumber", types::Type::Int32Type());
+            AddTypeProperty(this, type, "columnNumber", types::Type::Int32Type());
+            AddTypeProperty(this, type, "stack", types::Type::StringType());
+        }
     }
 
     /*
      * The new type is not present in any type sets, so mark the object as
      * unknown in all type sets it appears in. This allows the prototype of
      * such objects to mutate freely without triggering an expensive walk of
      * the compartment's type sets. (While scripts normally don't mutate
      * __proto__, the browser will for proxies and such, and we need to
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -144,22 +144,20 @@ js::TransparentObjectWrapper(JSContext *
     return Wrapper::New(cx, obj, wrappedProto, parent, &CrossCompartmentWrapper::singleton);
 }
 
 ErrorCopier::~ErrorCopier()
 {
     JSContext *cx = ac.ref().context()->asJSContext();
     if (ac.ref().origin() != cx->compartment() && cx->isExceptionPending()) {
         RootedValue exc(cx, cx->getPendingException());
-        if (exc.isObject() && exc.toObject().is<ErrorObject>() &&
-            exc.toObject().as<ErrorObject>().getExnPrivate())
-        {
+        if (exc.isObject() && exc.toObject().is<ErrorObject>()) {
             cx->clearPendingException();
             ac.destroy();
-            Rooted<JSObject*> errObj(cx, &exc.toObject());
+            Rooted<ErrorObject*> errObj(cx, &exc.toObject().as<ErrorObject>());
             JSObject *copyobj = js_CopyErrorObject(cx, errObj, scope);
             if (copyobj)
                 cx->setPendingException(ObjectValue(*copyobj));
         }
     }
 }
 
 /* Cross compartment wrappers. */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -155,16 +155,17 @@ UNIFIED_SOURCES += [
     'jswrapper.cpp',
     'perf/jsperf.cpp',
     'prmjtime.cpp',
     'vm/ArgumentsObject.cpp',
     'vm/CallNonGenericMethod.cpp',
     'vm/CharacterEncoding.cpp',
     'vm/DateTime.cpp',
     'vm/Debugger.cpp',
+    'vm/ErrorObject.cpp',
     'vm/ForkJoin.cpp',
     'vm/GlobalObject.cpp',
     'vm/Id.cpp',
     'vm/Interpreter.cpp',
     'vm/MemoryMetrics.cpp',
     'vm/Monitor.cpp',
     'vm/ObjectImpl.cpp',
     'vm/OldDebugAPI.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/vm/ErrorObject.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ *
+ * 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.h"
+
+#include "vm/GlobalObject.h"
+
+#include "jsobjinlines.h"
+
+using namespace js;
+
+/* static */ Shape *
+js::ErrorObject::assignInitialShapeNoMessage(JSContext *cx, Handle<ErrorObject*> obj)
+{
+    MOZ_ASSERT(obj->nativeEmpty());
+
+    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);
+}
+
+/* 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,
+                      HandleString message)
+{
+    // Null out early in case of error, for exn_finalize's sake.
+    obj->initReservedSlot(ERROR_REPORT_SLOT, PrivateValue(nullptr));
+
+    if (obj->nativeEmpty()) {
+        // Create the initial shape now.  Subsequent error objects with this
+        // object's [[Prototype]] will then start life with that shape.
+        RootedShape shape(cx, ErrorObject::assignInitialShapeNoMessage(cx, obj));
+        if (!shape)
+            return false;
+        if (!obj->isDelegate()) {
+            RootedObject proto(cx, obj->getProto());
+            EmptyShape::insertInitialShape(cx, shape, proto);
+        }
+        MOZ_ASSERT(!obj->nativeEmpty());
+    }
+
+    // The .message property isn't part of the initial shape because it's
+    // present in some error objects -- |Error.prototype|, |new Error("f")|,
+    // |new Error("")| -- but not in others -- |new Error(undefined)|,
+    // |new Error()|.
+    RootedShape messageShape(cx);
+    if (message) {
+        messageShape = obj->addDataProperty(cx, cx->names().message, MESSAGE_SLOT, 0);
+        if (!messageShape)
+            return false;
+        MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT);
+    }
+
+    MOZ_ASSERT(obj->nativeLookupPure(NameToId(cx->names().fileName))->slot() == FILENAME_SLOT);
+    MOZ_ASSERT(obj->nativeLookupPure(NameToId(cx->names().lineNumber))->slot() == LINENUMBER_SLOT);
+    MOZ_ASSERT(obj->nativeLookupPure(NameToId(cx->names().columnNumber))->slot() ==
+               COLUMNNUMBER_SLOT);
+    MOZ_ASSERT(obj->nativeLookupPure(NameToId(cx->names().stack))->slot() == STACK_SLOT);
+    MOZ_ASSERT_IF(message,
+                  obj->nativeLookupPure(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->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->nativeSetSlotWithType(cx, messageShape, StringValue(message));
+
+    if (report && report->originPrincipals)
+        JS_HoldPrincipals(report->originPrincipals);
+
+    return true;
+}
+
+/* static */ ErrorObject *
+js::ErrorObject::create(JSContext *cx, JSExnType errorType, HandleString stack,
+                        HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
+                        ScopedJSFreePtr<JSErrorReport> *report, HandleString message)
+{
+    Rooted<JSObject*> proto(cx, cx->global()->getOrCreateCustomErrorPrototype(cx, errorType));
+    if (!proto)
+        return nullptr;
+
+    Rooted<ErrorObject*> errObject(cx);
+    {
+        JSObject* obj = NewObjectWithGivenProto(cx, &ErrorObject::class_, proto, nullptr);
+        if (!obj)
+            return nullptr;
+        errObject = &obj->as<ErrorObject>();
+    }
+
+    if (!ErrorObject::init(cx, errObject, errorType, report, fileName, stack,
+                           lineNumber, columnNumber, message))
+    {
+        return nullptr;
+    }
+
+    return errObject;
+}
--- a/js/src/vm/ErrorObject.h
+++ b/js/src/vm/ErrorObject.h
@@ -6,21 +6,91 @@
 
 #ifndef vm_ErrorObject_h_
 #define vm_ErrorObject_h_
 
 #include "jsobj.h"
 
 struct JSExnPrivate;
 
+/*
+ * Initialize the exception constructor/prototype hierarchy.
+ */
+extern JSObject *
+js_InitExceptionClasses(JSContext *cx, JS::HandleObject obj);
+
 namespace js {
 
 class ErrorObject : public JSObject
 {
+    static ErrorObject *
+    createProto(JSContext *cx, JS::Handle<GlobalObject*> global, JSExnType type,
+                JS::HandleObject proto);
+
+    /* For access to createProto. */
+    friend JSObject *
+    ::js_InitExceptionClasses(JSContext *cx, JS::HandleObject global);
+
+    static Shape *
+    assignInitialShapeNoMessage(JSContext *cx, Handle<ErrorObject*> obj);
+
+    static bool
+    init(JSContext *cx, Handle<ErrorObject*> obj, JSExnType type,
+         ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName, HandleString stack,
+         uint32_t lineNumber, uint32_t columnNumber, HandleString message);
+
+  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 RESERVED_SLOTS = MESSAGE_SLOT + 1;
+
   public:
     static const Class class_;
 
-    JSExnPrivate *getExnPrivate() { return static_cast<JSExnPrivate*>(getPrivate()); }
+    // 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,
+           uint32_t lineNumber, uint32_t columnNumber, ScopedJSFreePtr<JSErrorReport> *report,
+           HandleString message);
+
+    JSExnType type() const {
+        return JSExnType(getReservedSlot(EXNTYPE_SLOT).toInt32());
+    }
+
+    JSErrorReport * getErrorReport() const {
+        void *priv = getReservedSlot(ERROR_REPORT_SLOT).toPrivate();
+        return static_cast<JSErrorReport*>(priv);
+    }
+
+    JSString * fileName() const {
+        return getReservedSlot(FILENAME_SLOT).toString();
+    }
+
+    uint32_t lineNumber() const {
+        return getReservedSlot(LINENUMBER_SLOT).toInt32();
+    }
+
+    uint32_t columnNumber() const {
+        return getReservedSlot(COLUMNNUMBER_SLOT).toInt32();
+    }
+
+    JSString * stack() const {
+        return getReservedSlot(STACK_SLOT).toString();
+    }
+
+    JSString * getMessage() const {
+        HeapSlot &slot = const_cast<ErrorObject*>(this)->getReservedSlotRef(MESSAGE_SLOT);
+        return slot.isString() ? slot.toString() : nullptr;
+    }
 };
 
 } // namespace js
 
 #endif // vm_ErrorObject_h_
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -10,16 +10,17 @@
 #include "jsarray.h"
 #include "jsbool.h"
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsnum.h"
 
 #include "builtin/RegExp.h"
 #include "js/Vector.h"
+#include "vm/ErrorObject.h"
 
 extern JSObject *
 js_InitObjectClass(JSContext *cx, js::HandleObject obj);
 
 extern JSObject *
 js_InitFunctionClass(JSContext *cx, js::HandleObject obj);
 
 extern JSObject *
@@ -392,17 +393,17 @@ class GlobalObject : public JSObject
         if (arrayBufferClassInitialized())
             return &getPrototype(JSProto_ArrayBuffer).toObject();
         Rooted<GlobalObject*> self(cx, this);
         if (!js_InitTypedArrayClasses(cx, self))
             return nullptr;
         return &self->getPrototype(JSProto_ArrayBuffer).toObject();
     }
 
-    JSObject *getOrCreateCustomErrorPrototype(JSContext *cx, int exnType) {
+    JSObject *getOrCreateCustomErrorPrototype(JSContext *cx, JSExnType exnType) {
         JSProtoKey key = GetExceptionProtoKey(exnType);
         if (errorClassesInitialized())
             return &getPrototype(key).toObject();
         Rooted<GlobalObject*> self(cx, this);
         if (!js_InitExceptionClasses(cx, self))
             return nullptr;
         return &self->getPrototype(key).toObject();
     }
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2800,18 +2800,16 @@ function ObjectActor(aObj, aThreadActor)
 {
   this.obj = aObj;
   this.threadActor = aThreadActor;
 }
 
 ObjectActor.prototype = {
   actorPrefix: "obj",
 
-  _forcedMagicProps: false,
-
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   grip: function () {
     let g = {
       "type": "object",
       "class": this.obj.class,
       "actor": this.actorID,
@@ -2852,37 +2850,16 @@ ObjectActor.prototype = {
   release: function () {
     if (this.registeredPool.objectActors) {
       this.registeredPool.objectActors.delete(this.obj);
     }
     this.registeredPool.removeActor(this);
   },
 
   /**
-   * Force the magic Error properties to appear.
-   */
-  _forceMagicProperties: function () {
-    if (this._forcedMagicProps) {
-      return;
-    }
-
-    const MAGIC_ERROR_PROPERTIES = [
-      "message", "stack", "fileName", "lineNumber", "columnNumber"
-    ];
-
-    if (this.obj.class.endsWith("Error")) {
-      for (let property of MAGIC_ERROR_PROPERTIES) {
-        this._propertyDescriptor(property);
-      }
-    }
-
-    this._forcedMagicProps = true;
-  },
-
-  /**
    * Handle a protocol request to provide the definition site of this function
    * object.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onDefinitionSite: function OA_onDefinitionSite(aRequest) {
     if (this.obj.class != "Function") {
@@ -2922,30 +2899,28 @@ ObjectActor.prototype = {
   /**
    * Handle a protocol request to provide the names of the properties defined on
    * the object and not its prototype.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onOwnPropertyNames: function (aRequest) {
-    this._forceMagicProperties();
     return { from: this.actorID,
              ownPropertyNames: this.obj.getOwnPropertyNames() };
   },
 
   /**
    * Handle a protocol request to provide the prototype and own properties of
    * the object.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onPrototypeAndProperties: function (aRequest) {
-    this._forceMagicProperties();
     let ownProperties = Object.create(null);
     let names;
     try {
       names = this.obj.getOwnPropertyNames();
     } catch (ex) {
       // The above can throw if this.obj points to a dead object.
       // TODO: we should use Cu.isDeadWrapper() - see bug 885800.
       return { from: this.actorID,
--- a/toolkit/devtools/server/tests/unit/test_objectgrips-11.js
+++ b/toolkit/devtools/server/tests/unit/test_objectgrips-11.js
@@ -27,22 +27,24 @@ 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) {
-      do_check_eq(aResponse.ownPropertyNames.length, 5);
-      do_check_eq(aResponse.ownPropertyNames[0], "message");
-      do_check_eq(aResponse.ownPropertyNames[1], "stack");
-      do_check_eq(aResponse.ownPropertyNames[2], "fileName");
-      do_check_eq(aResponse.ownPropertyNames[3], "lineNumber");
-      do_check_eq(aResponse.ownPropertyNames[4], "columnNumber");
+      var opn = aResponse.ownPropertyNames;
+      do_check_eq(opn.length, 5);
+      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);
       });
     });
 
   });