Bug 1213341 - Use ordinary objects for Error prototypes. r=arai
authorTom Schuster <evilpies@gmail.com>
Tue, 22 Nov 2016 20:53:38 +0100
changeset 371113 4e6fc50106d39c1c57555f7bdda8e336ba8a91b3
parent 371112 25dbaa4741412f9f4757553c2762e00e8ed0d09d
child 371114 4bcfbda6fe3dddbddf64ec65de4ebd6acda2570d
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1213341
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1213341 - Use ordinary objects for Error prototypes. r=arai
js/src/jsapi.h
js/src/jsexn.cpp
js/src/vm/ErrorObject.h
js/src/vm/GlobalObject.h
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -645,17 +645,18 @@ typedef enum JSExnType {
         JSEXN_RANGEERR,
         JSEXN_REFERENCEERR,
         JSEXN_SYNTAXERR,
         JSEXN_TYPEERR,
         JSEXN_URIERR,
         JSEXN_DEBUGGEEWOULDRUN,
         JSEXN_WASMCOMPILEERROR,
         JSEXN_WASMRUNTIMEERROR,
-    JSEXN_WARN,
+    JSEXN_ERROR_LIMIT,
+    JSEXN_WARN = JSEXN_ERROR_LIMIT,
     JSEXN_LIMIT
 } JSExnType;
 
 typedef struct JSErrorFormatString {
      /** The error message name in ASCII. */
     const char* name;
 
     /** The error format string in ASCII. */
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -43,111 +43,167 @@ 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
+#define IMPLEMENT_ERROR_PROTO_CLASS(name) \
+    { \
+        js_Object_str, \
+        JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
+        JS_NULL_CLASS_OPS, \
+        &ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
+    }
+
+const Class
+ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
+    IMPLEMENT_ERROR_PROTO_CLASS(Error),
+
+    IMPLEMENT_ERROR_PROTO_CLASS(InternalError),
+    IMPLEMENT_ERROR_PROTO_CLASS(EvalError),
+    IMPLEMENT_ERROR_PROTO_CLASS(RangeError),
+    IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError),
+    IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError),
+    IMPLEMENT_ERROR_PROTO_CLASS(TypeError),
+    IMPLEMENT_ERROR_PROTO_CLASS(URIError),
+
+    IMPLEMENT_ERROR_PROTO_CLASS(DebuggeeWouldRun),
+    IMPLEMENT_ERROR_PROTO_CLASS(CompileError),
+    IMPLEMENT_ERROR_PROTO_CLASS(RuntimeError)
 };
 
-static const JSFunctionSpec exception_methods[] = {
+static const JSFunctionSpec error_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
 };
 
+static const JSPropertySpec error_properties[] = {
+    JS_STRING_PS("message", "", 0),
+    JS_STRING_PS("name", "Error", 0),
+    // Only Error.prototype has .stack!
+    JS_PSGS("stack", ErrorObject::getStack, ErrorObject::setStack, 0),
+    JS_PS_END
+};
+
+#define IMPLEMENT_ERROR_PROPERTIES(name) \
+    { \
+        JS_STRING_PS("message", "", 0), \
+        JS_STRING_PS("name", #name, 0), \
+        JS_PS_END \
+    }
+
+static const JSPropertySpec other_error_properties[JSEXN_ERROR_LIMIT - 1][3] = {
+    IMPLEMENT_ERROR_PROPERTIES(InternalError),
+    IMPLEMENT_ERROR_PROPERTIES(EvalError),
+    IMPLEMENT_ERROR_PROPERTIES(RangeError),
+    IMPLEMENT_ERROR_PROPERTIES(ReferenceError),
+    IMPLEMENT_ERROR_PROPERTIES(SyntaxError),
+    IMPLEMENT_ERROR_PROPERTIES(TypeError),
+    IMPLEMENT_ERROR_PROPERTIES(URIError),
+    IMPLEMENT_ERROR_PROPERTIES(DebuggeeWouldRun),
+    IMPLEMENT_ERROR_PROPERTIES(CompileError),
+    IMPLEMENT_ERROR_PROPERTIES(RuntimeError)
+};
+
+#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
+    { \
+        ErrorObject::createConstructor, \
+        ErrorObject::createProto, \
+        nullptr, \
+        nullptr, \
+        nullptr, \
+        other_error_properties[JSProto_##name - JSProto_Error - 1], \
+        nullptr, \
+        JSProto_Error \
+    }
+
+#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
+    { \
+        ErrorObject::createConstructor, \
+        ErrorObject::createProto, \
+        nullptr, \
+        nullptr, \
+        nullptr, \
+        other_error_properties[JSProto_##name - JSProto_Error - 1], \
+        nullptr, \
+        JSProto_Error | ClassSpec::DontDefineConstructor \
+    }
+
+const ClassSpec
+ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
+    {
+        ErrorObject::createConstructor,
+        ErrorObject::createProto,
+        nullptr,
+        nullptr,
+        error_methods,
+        error_properties
+    },
+
+    IMPLEMENT_NATIVE_ERROR_SPEC(InternalError),
+    IMPLEMENT_NATIVE_ERROR_SPEC(EvalError),
+    IMPLEMENT_NATIVE_ERROR_SPEC(RangeError),
+    IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError),
+    IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError),
+    IMPLEMENT_NATIVE_ERROR_SPEC(TypeError),
+    IMPLEMENT_NATIVE_ERROR_SPEC(URIError),
+
+    IMPLEMENT_NONGLOBAL_ERROR_SPEC(DebuggeeWouldRun),
+    IMPLEMENT_NONGLOBAL_ERROR_SPEC(CompileError),
+    IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError)
+};
+
+#define IMPLEMENT_ERROR_CLASS(name) \
+    { \
+        js_Error_str, /* yes, really */ \
+        JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
+        JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \
+        JSCLASS_BACKGROUND_FINALIZE, \
+        &ErrorObjectClassOps, \
+        &ErrorObject::classSpecs[JSProto_##name - JSProto_Error ] \
+    }
+
 static const ClassOps ErrorObjectClassOps = {
     nullptr,                 /* addProperty */
     nullptr,                 /* delProperty */
     nullptr,                 /* getProperty */
     nullptr,                 /* setProperty */
     nullptr,                 /* enumerate */
     nullptr,                 /* resolve */
     nullptr,                 /* mayResolve */
     exn_finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     nullptr,                 /* trace       */
 };
 
-#define IMPLEMENT_ERROR_CLASS(name, classSpecPtr) \
-    { \
-        js_Error_str, /* yes, really */ \
-        JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
-        JSCLASS_HAS_RESERVED_SLOTS(ErrorObject::RESERVED_SLOTS) | \
-        JSCLASS_BACKGROUND_FINALIZE, \
-        &ErrorObjectClassOps, \
-        classSpecPtr \
-    }
-
-const ClassSpec
-ErrorObject::errorClassSpec_ = {
-    ErrorObject::createConstructor,
-    ErrorObject::createProto,
-    nullptr,
-    nullptr,
-    exception_methods,
-    exception_properties,
-    nullptr,
-    0
-};
-
-const ClassSpec
-ErrorObject::subErrorClassSpec_ = {
-    ErrorObject::createConstructor,
-    ErrorObject::createProto,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
-    JSProto_Error
-};
-
-const ClassSpec
-ErrorObject::nonGlobalErrorClassSpec_ = {
-    ErrorObject::createConstructor,
-    ErrorObject::createProto,
-    nullptr,
-    nullptr,
-    exception_methods,
-    exception_properties,
-    nullptr,
-    JSProto_Error | ClassSpec::DontDefineConstructor
-};
-
 const Class
-ErrorObject::classes[JSEXN_LIMIT] = {
-    IMPLEMENT_ERROR_CLASS(Error,          &ErrorObject::errorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(InternalError,  &ErrorObject::subErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(EvalError,      &ErrorObject::subErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(RangeError,     &ErrorObject::subErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(ReferenceError, &ErrorObject::subErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(SyntaxError,    &ErrorObject::subErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(TypeError,      &ErrorObject::subErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(URIError,       &ErrorObject::subErrorClassSpec_),
-
+ErrorObject::classes[JSEXN_ERROR_LIMIT] = {
+    IMPLEMENT_ERROR_CLASS(Error),
+    IMPLEMENT_ERROR_CLASS(InternalError),
+    IMPLEMENT_ERROR_CLASS(EvalError),
+    IMPLEMENT_ERROR_CLASS(RangeError),
+    IMPLEMENT_ERROR_CLASS(ReferenceError),
+    IMPLEMENT_ERROR_CLASS(SyntaxError),
+    IMPLEMENT_ERROR_CLASS(TypeError),
+    IMPLEMENT_ERROR_CLASS(URIError),
     // These Error subclasses are not accessible via the global object:
-    IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun, &ErrorObject::nonGlobalErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(CompileError,   &ErrorObject::nonGlobalErrorClassSpec_),
-    IMPLEMENT_ERROR_CLASS(RuntimeError,   &ErrorObject::nonGlobalErrorClassSpec_)
+    IMPLEMENT_ERROR_CLASS(DebuggeeWouldRun),
+    IMPLEMENT_ERROR_CLASS(CompileError),
+    IMPLEMENT_ERROR_CLASS(RuntimeError)
 };
 
 JSErrorReport*
 js::CopyErrorReport(JSContext* cx, JSErrorReport* report)
 {
     /*
      * We use a single malloc block to make a deep copy of JSErrorReport with
      * the following layout:
@@ -449,45 +505,50 @@ exn_toSource(JSContext* cx, unsigned arg
     args.rval().setString(str);
     return true;
 }
 #endif
 
 /* static */ JSObject*
 ErrorObject::createProto(JSContext* cx, JSProtoKey key)
 {
-    RootedObject errorProto(cx, GenericCreatePrototype(cx, key));
-    if (!errorProto)
+    JSExnType type = ExnTypeFromProtoKey(key);
+
+    if (type == JSEXN_ERR)
+        return cx->global()->createBlankPrototype(cx, &ErrorObject::protoClasses[JSEXN_ERR]);
+
+    RootedObject protoProto(cx, GlobalObject::getOrCreateErrorPrototype(cx, cx->global()));
+    if (!protoProto)
         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, 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;
-
-    return errorProto;
+    return cx->global()->createBlankPrototypeInheriting(cx, &ErrorObject::protoClasses[type],
+                                                        protoProto);
 }
 
 /* static */ JSObject*
 ErrorObject::createConstructor(JSContext* cx, JSProtoKey key)
 {
+    JSExnType type = ExnTypeFromProtoKey(key);
     RootedObject ctor(cx);
-    ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key);
+
+    if (type == JSEXN_ERR) {
+        ctor = GenericCreateConstructor<Error, 1, gc::AllocKind::FUNCTION_EXTENDED>(cx, key);
+    } else {
+        RootedFunction proto(cx, GlobalObject::getOrCreateErrorConstructor(cx, cx->global()));
+        if (!proto)
+            return nullptr;
+
+        ctor = NewFunctionWithProto(cx, Error, 1, JSFunction::NATIVE_CTOR, nullptr,
+                                    ClassName(key, cx), proto, gc::AllocKind::FUNCTION_EXTENDED);
+    }
+
     if (!ctor)
         return nullptr;
 
-    ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key)));
+    ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(type));
     return ctor;
 }
 
 JS_FRIEND_API(JSFlatString*)
 js::GetErrorTypeName(JSContext* cx, int16_t exnType)
 {
     /*
      * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
--- a/js/src/vm/ErrorObject.h
+++ b/js/src/vm/ErrorObject.h
@@ -33,33 +33,32 @@ class ErrorObject : public NativeObject
     friend JSObject*
     js::InitExceptionClasses(JSContext* cx, HandleObject global);
 
     static bool
     init(JSContext* cx, Handle<ErrorObject*> obj, JSExnType type,
          ScopedJSFreePtr<JSErrorReport>* errorReport, HandleString fileName, HandleObject stack,
          uint32_t lineNumber, uint32_t columnNumber, HandleString message);
 
-    static const ClassSpec errorClassSpec_;
-    static const ClassSpec subErrorClassSpec_;
-    static const ClassSpec nonGlobalErrorClassSpec_;
+    static const ClassSpec classSpecs[JSEXN_ERROR_LIMIT];
+    static const Class protoClasses[JSEXN_ERROR_LIMIT];
 
   protected:
     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 classes[JSEXN_ERROR_LIMIT];
 
     static const Class * classForType(JSExnType type) {
         MOZ_ASSERT(type < JSEXN_WARN);
         return &classes[type];
     }
 
     static bool isErrorClass(const Class* clasp) {
         return &classes[0] <= clasp && clasp < &classes[0] + mozilla::ArrayLength(classes);
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -422,16 +422,28 @@ class GlobalObject : public NativeObject
                                                      JSExnType exnType)
     {
         JSProtoKey key = GetExceptionProtoKey(exnType);
         if (!ensureConstructor(cx, global, key))
             return nullptr;
         return &global->getPrototype(key).toObject();
     }
 
+    static JSFunction*
+    getOrCreateErrorConstructor(JSContext* cx, Handle<GlobalObject*> global) {
+        if (!ensureConstructor(cx, global, JSProto_Error))
+            return nullptr;
+        return &global->getConstructor(JSProto_Error).toObject().as<JSFunction>();
+    }
+
+    static JSObject*
+    getOrCreateErrorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
+        return getOrCreateCustomErrorPrototype(cx, global, JSEXN_ERR);
+    }
+
     static NativeObject* getOrCreateSetPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         if (!ensureConstructor(cx, global, JSProto_Set))
             return nullptr;
         return &global->getPrototype(JSProto_Set).toObject().as<NativeObject>();
     }
 
     static NativeObject* getOrCreateWeakSetPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         if (!ensureConstructor(cx, global, JSProto_WeakSet))
@@ -1000,20 +1012,17 @@ GenericCreatePrototype(JSContext* cx, JS
         return nullptr;
     RootedObject parentProto(cx, &cx->global()->getPrototype(protoKey).toObject());
     return cx->global()->createBlankPrototypeInheriting(cx, clasp, parentProto);
 }
 
 inline JSProtoKey
 StandardProtoKeyOrNull(const JSObject* obj)
 {
-    JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass());
-    if (key == JSProto_Error)
-        return GetExceptionProtoKey(obj->as<ErrorObject>().type());
-    return key;
+    return JSCLASS_CACHED_PROTO_KEY(obj->getClass());
 }
 
 JSObject*
 NewSingletonObjectWithFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global);
 
 } // namespace js
 
 template<>