diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index ba2cd99e80f..4b9c96215de 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2554,6 +2554,32 @@ SetDiscardSource(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +GetConstructorName(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.requireAtLeast(cx, "getConstructorName", 1)) + return false; + + if (!args[0].isObject()) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, + "getConstructorName", "Object", + InformalValueTypeName(args[0])); + return false; + } + + RootedAtom name(cx); + if (!args[0].toObject().constructorDisplayAtom(cx, &name)) + return false; + + if (name) { + args.rval().setString(name); + } else { + args.rval().setNull(); + } + return true; +} + static const JSFunctionSpecWithHelp TestingFunctions[] = { JS_FN_HELP("gc", ::GC, 0, 0, "gc([obj] | 'compartment' [, 'shrinking'])", @@ -2960,6 +2986,11 @@ gc::ZealModeHelpText), " Explicitly enable source discarding in the current compartment. The default is that " " source discarding is not explicitly enabled."), + JS_FN_HELP("getConstructorName", GetConstructorName, 1, 0, +"getConstructorName(object)", +" If the given object was created with `new Ctor`, return the constructor's display name. " +" Otherwise, return null."), + JS_FS_HELP_END }; diff --git a/js/src/jit-test/tests/basic/constructor-name.js b/js/src/jit-test/tests/basic/constructor-name.js new file mode 100644 index 00000000000..4bc6a61ead0 --- /dev/null +++ b/js/src/jit-test/tests/basic/constructor-name.js @@ -0,0 +1,28 @@ +function Ctor() {} + +var nested = {}; +nested.Ctor = function () {}; +nested.object = {}; + +function makeInstance() { + let LexicalCtor = function () {}; + return new LexicalCtor; +} + +function makeObject() { + let object = {}; + return object; +} + +let tests = [ + { name: "Ctor", object: new Ctor }, + { name: "nested.Ctor", object: new nested.Ctor }, + { name: "makeInstance/LexicalCtor", object: makeInstance() }, + { name: null, object: {} }, + { name: null, object: nested.object }, + { name: null, object: makeObject() }, +]; + +for (let { name, object } of tests) { + assertEq(getConstructorName(object), name); +} diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index ec935dab1d7..21fadd3e325 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -4106,3 +4106,32 @@ JSObject::traceChildren(JSTracer* trc) } while (false); } } + +static JSAtom* +displayAtomFromObjectGroup(ObjectGroup& group) +{ + TypeNewScript* script = group.newScript(); + if (!script) + return nullptr; + + return script->function()->displayAtom(); +} + +bool +JSObject::constructorDisplayAtom(JSContext* cx, js::MutableHandleAtom name) +{ + ObjectGroup *g = getGroup(cx); + if (!g) + return false; + + name.set(displayAtomFromObjectGroup(*g)); + return true; +} + +JSAtom* +JSObject::maybeConstructorDisplayAtom() const +{ + if (hasLazyGroup()) + return nullptr; + return displayAtomFromObjectGroup(*group()); +} diff --git a/js/src/jsobj.h b/js/src/jsobj.h index d37ff91b58c..46b4972efed 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -254,6 +254,20 @@ class JSObject : public js::gc::Cell */ inline bool isIndexed() const; + /* + * If this object was instantiated with `new Ctor`, return the constructor's + * display atom. Otherwise, return nullptr. + */ + bool constructorDisplayAtom(JSContext* cx, js::MutableHandleAtom name); + + /* + * The same as constructorDisplayAtom above, however if this object has a + * lazy group, nullptr is returned. This allows for use in situations that + * cannot GC and where having some information, even if it is inconsistently + * available, is better than no information. + */ + JSAtom* maybeConstructorDisplayAtom() const; + /* GC support. */ void traceChildren(JSTracer* trc);