From 53d6173cfb7fbb0cad43df2110fca9f405345f0b Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 1 Jul 2022 12:13:42 +0200 Subject: [PATCH 1/6] Issue #1952 - m-c 1365387: Optimize FinishBoundFunctionInit by delaying atomizing the function name --- js/src/jsfun.cpp | 58 ++++++++++++++++++++++++++++----------- js/src/jsfun.h | 3 +- js/src/vm/SelfHosting.cpp | 38 ++++++++++--------------- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index be1d39cd19..0d0a899b03 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -339,11 +339,11 @@ CallerSetterImpl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(IsFunction(args.thisv())); - // We just have to return |undefined|, but first we call CallerGetterImpl - // because we need the same strict-mode and security checks. - + // We just have to return |undefined|, but first we call CallerGetterImpl + // because we need the same strict-mode and security checks. + if (!CallerGetterImpl(cx, args)) { - return false; + return false; } args.rval().setUndefined(); @@ -498,9 +498,12 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) if (fun->hasResolvedName()) return true; + RootedAtom name(cx); + if (!JSFunction::getUnresolvedName(cx, fun, &name)) + return false; + // Don't define an own .name property for unnamed functions. - JSAtom* name = fun->getUnresolvedName(cx); - if (name == nullptr) + if (!name) return true; v.setString(name); @@ -997,7 +1000,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) return nullptr; } - auto AppendPrelude = [&out, &fun]() { + auto AppendPrelude = [cx, &out, &fun]() { if (fun->isAsync()) { if (!out.append("async ")) return false; @@ -1016,6 +1019,10 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) if (fun->explicitName()) { if (!out.append(' ')) return false; + if (fun->isBoundFunction()) { + if (!out.append(cx->names().boundWithSpace)) + return false; + } if (!out.append(fun->explicitName())) return false; } @@ -1327,23 +1334,42 @@ JSFunction::getUnresolvedLength(JSContext* cx, HandleFunction fun, MutableHandle return true; } -JSAtom* -JSFunction::getUnresolvedName(JSContext* cx) +/* static */ bool +JSFunction::getUnresolvedName(JSContext* cx, HandleFunction fun, MutableHandleAtom v) { - MOZ_ASSERT(!IsInternalFunctionObject(*this)); - MOZ_ASSERT(!hasResolvedName()); + MOZ_ASSERT(!IsInternalFunctionObject(*fun)); + MOZ_ASSERT(!fun->hasResolvedName()); - if (isClassConstructor()) { + JSAtom* name = fun->explicitOrCompileTimeName(); + if (fun->isClassConstructor()) { // It's impossible to have an empty named class expression. We use // empty as a sentinel when creating default class constructors. - MOZ_ASSERT(explicitOrCompileTimeName() != cx->names().empty); + MOZ_ASSERT(name != cx->names().empty); // Unnamed class expressions should not get a .name property at all. - return explicitOrCompileTimeName(); + if (name) + v.set(name); + return true; } - return explicitOrCompileTimeName() != nullptr ? explicitOrCompileTimeName() - : cx->names().empty; + if (fun->isBoundFunction()) { + // Bound functions are never unnamed. + MOZ_ASSERT(name); + + StringBuffer sb(cx); + if (!sb.append(cx->names().boundWithSpace) || !sb.append(name)) + return false; + + JSAtom* boundName = sb.finishAtom(); + if (!boundName) + return false; + + v.set(boundName); + return true; + } + + v.set(name != nullptr ? name : cx->names().empty); + return true; } static const js::Value& diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 38cc832800..2bc846b07a 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -310,7 +310,8 @@ class JSFunction : public js::NativeObject static bool getUnresolvedLength(JSContext* cx, js::HandleFunction fun, js::MutableHandleValue v); - JSAtom* getUnresolvedName(JSContext* cx); + static bool getUnresolvedName(JSContext* cx, js::HandleFunction fun, + js::MutableHandleAtom v); JSAtom* explicitName() const { return (hasCompileTimeName() || hasGuessedAtom()) ? nullptr : atom_.get(); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 38785a822c..29c75c2064 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -571,39 +571,31 @@ intrinsic_FinishBoundFunctionInit(JSContext* cx, unsigned argc, Value* vp) bound->setExtendedSlot(BOUND_FUN_LENGTH_SLOT, NumberValue(length)); // Try to avoid invoking the resolve hook. - JSAtom* name = nullptr; - if (targetObj->is() && !targetObj->as().hasResolvedName()) - name = targetObj->as().getUnresolvedName(cx); + RootedAtom name(cx); + if (targetObj->is() && !targetObj->as().hasResolvedName()) { + if (!JSFunction::getUnresolvedName(cx, targetObj.as(), &name)) + return false; + } - RootedString rootedName(cx); - if (name) { - rootedName = name; - } else { + // 19.2.3.2 Function.prototype.bind, steps 9-11. + if (!name) { // 19.2.3.2 Function.prototype.bind, step 9. RootedValue targetName(cx); if (!GetProperty(cx, targetObj, targetObj, cx->names().name, &targetName)) return false; // 19.2.3.2 Function.prototype.bind, step 10. - if (targetName.isString()) - rootedName = targetName.toString(); + if (targetName.isString() && !targetName.toString()->empty()) { + name = AtomizeString(cx, targetName.toString()); + if (!name) + return false; + } else { + name = cx->names().empty; + } } - // 19.2.3.2 Function.prototype.bind, step 11 (Inlined SetFunctionName). MOZ_ASSERT(!bound->hasGuessedAtom()); - if (rootedName && !rootedName->empty()) { - StringBuffer sb(cx); - if (!sb.append(cx->names().boundWithSpace) || !sb.append(rootedName)) - return false; - - RootedAtom nameAtom(cx, sb.finishAtom()); - if (!nameAtom) - return false; - - bound->setAtom(nameAtom); - } else { - bound->setAtom(cx->names().boundWithSpace); - } + bound->setAtom(name); args.rval().setUndefined(); return true; From fc6c505da880ffe5d7e3665d2c7849c0c3d9c2a8 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 1 Jul 2022 12:24:00 +0200 Subject: [PATCH 2/6] Issue #1952 - m-c 1371593: Avoid repeated string atomizations when retrieving the unresolved name of a bound function --- js/src/jsfun.cpp | 23 +++++++++++++---------- js/src/jsfun.h | 33 ++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 0d0a899b03..c409a0f29b 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1019,7 +1019,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) if (fun->explicitName()) { if (!out.append(' ')) return false; - if (fun->isBoundFunction()) { + if (fun->isBoundFunction() && !fun->hasBoundFunctionNamePrefix()) { if (!out.append(cx->names().boundWithSpace)) return false; } @@ -1352,20 +1352,23 @@ JSFunction::getUnresolvedName(JSContext* cx, HandleFunction fun, MutableHandleAt return true; } - if (fun->isBoundFunction()) { + if (fun->isBoundFunction() && !fun->hasBoundFunctionNamePrefix()) { // Bound functions are never unnamed. MOZ_ASSERT(name); - StringBuffer sb(cx); - if (!sb.append(cx->names().boundWithSpace) || !sb.append(name)) - return false; + if (name->length() > 0) { + StringBuffer sb(cx); + if (!sb.append(cx->names().boundWithSpace) || !sb.append(name)) + return false; - JSAtom* boundName = sb.finishAtom(); - if (!boundName) - return false; + name = sb.finishAtom(); + if (!name) + return false; + } else { + name = cx->names().boundWithSpace; + } - v.set(boundName); - return true; + fun->setPrefixedBoundFunctionName(name); } v.set(name != nullptr ? name : cx->names().empty); diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 2bc846b07a..ecdc2f2ebd 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -59,6 +59,9 @@ class JSFunction : public js::NativeObject BOUND_FUN = 0x0008, /* function was created with Function.prototype.bind. */ HAS_GUESSED_ATOM = 0x0020, /* function had no explicit name, but a name was guessed for it anyway */ + HAS_BOUND_FUNCTION_NAME_PREFIX = 0x0020, /* bound functions reuse the HAS_GUESSED_ATOM + flag to track if atom_ already contains the + "bound " function name prefix */ LAMBDA = 0x0040, /* function comes from a FunctionExpression, ArrowFunction, or Function() call (not a FunctionDeclaration or nonstandard function-statement) */ @@ -99,8 +102,8 @@ class JSFunction : public js::NativeObject INTERPRETED_GENERATOR_OR_ASYNC = INTERPRETED, NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, - STABLE_ACROSS_CLONES = CONSTRUCTOR | HAS_GUESSED_ATOM | LAMBDA | - SELF_HOSTED | HAS_COMPILE_TIME_NAME | FUNCTION_KIND_MASK + STABLE_ACROSS_CLONES = CONSTRUCTOR | LAMBDA | SELF_HOSTED | HAS_COMPILE_TIME_NAME | + FUNCTION_KIND_MASK }; static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, @@ -186,10 +189,20 @@ class JSFunction : public js::NativeObject bool isAsmJSNative() const { return kind() == AsmJS; } /* Possible attributes of an interpreted function: */ - bool hasCompileTimeName() const { return flags() & HAS_COMPILE_TIME_NAME; } - bool hasGuessedAtom() const { return flags() & HAS_GUESSED_ATOM; } - bool isLambda() const { return flags() & LAMBDA; } bool isBoundFunction() const { return flags() & BOUND_FUN; } + bool hasCompileTimeName() const { return flags() & HAS_COMPILE_TIME_NAME; } + bool hasGuessedAtom() const { + static_assert(HAS_GUESSED_ATOM == HAS_BOUND_FUNCTION_NAME_PREFIX, + "HAS_GUESSED_ATOM is unused for bound functions"); + return (flags() & (HAS_GUESSED_ATOM | BOUND_FUN)) == HAS_GUESSED_ATOM; + } + bool hasBoundFunctionNamePrefix() const { + static_assert(HAS_BOUND_FUNCTION_NAME_PREFIX == HAS_GUESSED_ATOM, + "HAS_BOUND_FUNCTION_NAME_PREFIX is only used for bound functions"); + MOZ_ASSERT(isBoundFunction()); + return flags() & HAS_BOUND_FUNCTION_NAME_PREFIX; + } + bool isLambda() const { return flags() & LAMBDA; } bool isInterpretedLazy() const { return flags() & INTERPRETED_LAZY; } bool hasScript() const { return flags() & INTERPRETED; } @@ -347,16 +360,26 @@ class JSFunction : public js::NativeObject MOZ_ASSERT(atom); MOZ_ASSERT(!hasCompileTimeName()); MOZ_ASSERT(!hasGuessedAtom()); + MOZ_ASSERT(!isBoundFunction()); + MOZ_ASSERT(js::AtomIsMarked(zone(), atom)); atom_ = atom; flags_ |= HAS_GUESSED_ATOM; } void clearGuessedAtom() { MOZ_ASSERT(hasGuessedAtom()); + MOZ_ASSERT(!isBoundFunction()); MOZ_ASSERT(atom_); atom_ = nullptr; flags_ &= ~HAS_GUESSED_ATOM; } + void setPrefixedBoundFunctionName(JSAtom* atom) { + MOZ_ASSERT(!hasBoundFunctionNamePrefix()); + MOZ_ASSERT(atom); + flags_ |= HAS_BOUND_FUNCTION_NAME_PREFIX; + atom_ = atom; + } + /* uint16_t representation bounds number of call object dynamic slots. */ enum { MAX_ARGS_AND_VARS = 2 * ((1U << 16) - 1) }; From 70eee9b0df31c043bbc548a78e754030147dbb6e Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 1 Jul 2022 12:27:03 +0200 Subject: [PATCH 3/6] Issue #1952 - m-c 1380962/1: Merge append calls when assembling the source string for native functions --- js/src/jsfun.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index c409a0f29b..e71a495028 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -990,7 +990,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) // If we're not in pretty mode, put parentheses around lambda functions // so that eval returns lambda, not function statement. if (haveSource && !prettyPrint && funIsNonArrowLambda) { - if (!out.append("(")) + if (!out.append('(')) return nullptr; } @@ -1038,7 +1038,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) return nullptr; if (!prettyPrint && funIsNonArrowLambda) { - if (!out.append(")")) + if (!out.append(')')) return nullptr; } } else if (fun->isInterpreted() && @@ -1056,22 +1056,14 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) !script->scriptSource()->sourceRetrievable() || fun->compartment()->behaviors().discardSource()); if (!AppendPrelude() || - !out.append("() {\n ") || - !out.append("[sourceless code]") || - !out.append("\n}")) + !out.append("() {\n [sourceless code]\n}")) { return nullptr; } } else { if (!AppendPrelude() || - !out.append("() {\n ")) - return nullptr; - - if (!out.append("[native code]")) - return nullptr; - - if (!out.append("\n}")) + !out.append("() {\n [native code]\n}")) return nullptr; } return out.finishString(); From 1e753237e5fd3887ce37df672991597d38467837 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 1 Jul 2022 12:44:12 +0200 Subject: [PATCH 4/6] Issue #1952 - m-c 1380962/2: Remove unnecessary lambda function in FunctionToString --- js/src/jit-test/tests/basic/bug1380962.js | 15 +++ js/src/jsfun.cpp | 114 +++++++++++----------- 2 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 js/src/jit-test/tests/basic/bug1380962.js diff --git a/js/src/jit-test/tests/basic/bug1380962.js b/js/src/jit-test/tests/basic/bug1380962.js new file mode 100644 index 0000000000..66548961d5 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug1380962.js @@ -0,0 +1,15 @@ +// Source string has balanced parentheses even when the source code was discarded. + +setDiscardSource(true); +eval("var f = function() { return 0; };"); +assertEq(f.toSource(), "(function() {\n [sourceless code]\n})"); +// Source string has balanced parentheses even when the source code was discarded. + +setDiscardSource(true); +eval("var f = function() { return 0; };"); +assertEq(f.toSource(), "(function() {\n [sourceless code]\n})"); +// Source string has balanced parentheses even when the source code was discarded. + +setDiscardSource(true); +eval("var f = function() { return 0; };"); +assertEq(f.toSource(), "(function() {\n [sourceless code]\n})"); diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index e71a495028..b4aeee51c2 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -978,8 +978,6 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) } } - bool funIsNonArrowLambda = fun->isLambda() && !fun->isArrow(); - // Default class constructors are self-hosted, but have their source // objects overridden to refer to the span of the class statement or // expression. Non-default class constructors are never self-hosted. So, @@ -989,10 +987,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) // If we're not in pretty mode, put parentheses around lambda functions // so that eval returns lambda, not function statement. - if (haveSource && !prettyPrint && funIsNonArrowLambda) { - if (!out.append('(')) - return nullptr; - } + bool addParentheses = haveSource && !prettyPrint && (fun->isLambda() && !fun->isArrow()); if (haveSource && !script->scriptSource()->hasSourceData() && !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) @@ -1000,34 +995,10 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) return nullptr; } - auto AppendPrelude = [cx, &out, &fun]() { - if (fun->isAsync()) { - if (!out.append("async ")) - return false; - } - - if (!fun->isArrow()) { - if (!out.append("function")) - return false; - - if (fun->isStarGenerator()) { - if (!out.append('*')) - return false; - } - } - - if (fun->explicitName()) { - if (!out.append(' ')) - return false; - if (fun->isBoundFunction() && !fun->hasBoundFunctionNamePrefix()) { - if (!out.append(cx->names().boundWithSpace)) - return false; - } - if (!out.append(fun->explicitName())) - return false; - } - return true; - }; + if (addParentheses) { + if (!out.append('(')) + return nullptr; + } if (haveSource) { Rooted src(cx, JSScript::sourceDataForToString(cx, script)); @@ -1036,36 +1007,61 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) if (!out.append(src)) return nullptr; - - if (!prettyPrint && funIsNonArrowLambda) { - if (!out.append(')')) + } else { + if (fun->isAsync()) { + if (!out.append("async ")) return nullptr; } - } else if (fun->isInterpreted() && - (!fun->isSelfHostedBuiltin() || - fun->infallibleIsDefaultClassConstructor(cx))) - { - // Default class constructors should always haveSource except; - // - // 1. Source has been discarded for the whole compartment. - // - // 2. The source is marked as "lazy", i.e., retrieved on demand, and - // the embedding has not provided a hook to retrieve sources. - MOZ_ASSERT_IF(fun->infallibleIsDefaultClassConstructor(cx), - !cx->runtime()->sourceHook || - !script->scriptSource()->sourceRetrievable() || - fun->compartment()->behaviors().discardSource()); - if (!AppendPrelude() || - !out.append("() {\n [sourceless code]\n}")) - { - return nullptr; - } - } else { - if (!AppendPrelude() || - !out.append("() {\n [native code]\n}")) + if (!fun->isArrow()) { + if (!out.append("function")) + return nullptr; + + if (fun->isStarGenerator()) { + if (!out.append('*')) + return nullptr; + } + } + + if (fun->explicitName()) { + if (!out.append(' ')) + return nullptr; + if (fun->isBoundFunction() && !fun->hasBoundFunctionNamePrefix()) { + if (!out.append(cx->names().boundWithSpace)) + return nullptr; + } + if (!out.append(fun->explicitName())) + return nullptr; + } + + if (fun->isInterpreted() && + (!fun->isSelfHostedBuiltin() || + fun->infallibleIsDefaultClassConstructor(cx))) + { + // Default class constructors should always haveSource except; + // + // 1. Source has been discarded for the whole compartment. + // + // 2. The source is marked as "lazy", i.e., retrieved on demand, and + // the embedding has not provided a hook to retrieve sources. + MOZ_ASSERT_IF(fun->infallibleIsDefaultClassConstructor(cx), + !cx->runtime()->sourceHook || + !script->scriptSource()->sourceRetrievable() || + fun->compartment()->behaviors().discardSource()); + + if (!out.append("() {\n [sourceless code]\n}")) + return nullptr; + } else { + if (!out.append("() {\n [native code]\n}")) + return nullptr; + } + } + + if (addParentheses) { + if (!out.append(')')) return nullptr; } + return out.finishString(); } From 85af7f6e80e316516fc81ed3402b798f395bf244 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 1 Jul 2022 12:53:23 +0200 Subject: [PATCH 5/6] Issue #1952 - m-c 1383775: Clean up function toString/toSource code, remove remnants of source decompiler --- dom/bindings/BindingUtils.cpp | 2 +- js/public/Class.h | 2 +- js/public/Proxy.h | 2 +- js/src/jsapi-tests/testXDR.cpp | 2 +- js/src/jsapi.cpp | 8 ++--- js/src/jsapi.h | 10 ++---- js/src/jsfriendapi.h | 2 +- js/src/jsfun.cpp | 31 ++++++++----------- js/src/jsfun.h | 4 +-- js/src/jsopcode.cpp | 4 +-- js/src/jswrapper.h | 7 +++-- js/src/proxy/BaseProxyHandler.cpp | 2 +- js/src/proxy/CrossCompartmentWrapper.cpp | 4 +-- js/src/proxy/DeadObjectProxy.cpp | 2 +- js/src/proxy/DeadObjectProxy.h | 3 +- .../proxy/OpaqueCrossCompartmentWrapper.cpp | 2 +- js/src/proxy/Proxy.cpp | 10 +++--- js/src/proxy/Proxy.h | 2 +- js/src/proxy/ScriptedProxyHandler.cpp | 2 +- js/src/proxy/ScriptedProxyHandler.h | 2 +- js/src/proxy/Wrapper.cpp | 4 +-- js/src/shell/js.cpp | 4 +-- js/src/wasm/AsmJS.cpp | 6 ++-- js/src/wasm/AsmJS.h | 2 +- 24 files changed, 55 insertions(+), 64 deletions(-) diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 512b9f8173..879db27a49 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -682,7 +682,7 @@ DefineUnforgeableAttributes(JSContext* cx, JS::Handle obj, // funToString ObjectOps member for interface objects. JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle aObject, - unsigned /* indent */) + bool /* isToSource */) { const js::Class* clasp = js::GetObjectClass(aObject); MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp)); diff --git a/js/public/Class.h b/js/public/Class.h index 1dd03a1821..634e5a281d 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -459,7 +459,7 @@ typedef bool * that object. A null return value means OOM. */ typedef JSString* -(* JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, unsigned indent); +(* JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, bool isToSource); /** * Resolve a lazy property named by id in obj by defining it directly in obj. diff --git a/js/public/Proxy.h b/js/public/Proxy.h index 8d24969b20..2453e2e140 100644 --- a/js/public/Proxy.h +++ b/js/public/Proxy.h @@ -326,7 +326,7 @@ class JS_FRIEND_API(BaseProxyHandler) ESClass* cls) const; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const; virtual const char* className(JSContext* cx, HandleObject proxy) const; - virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const; + virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const; virtual void trace(JSTracer* trc, JSObject* proxy) const; diff --git a/js/src/jsapi-tests/testXDR.cpp b/js/src/jsapi-tests/testXDR.cpp index 5d5fca1a99..3787f596ca 100644 --- a/js/src/jsapi-tests/testXDR.cpp +++ b/js/src/jsapi-tests/testXDR.cpp @@ -115,7 +115,7 @@ BEGIN_TEST(testXDR_source) CHECK(script); script = FreezeThaw(cx, script); CHECK(script); - JSString* out = JS_DecompileScript(cx, script, "testing", 0); + JSString* out = JS_DecompileScript(cx, script); CHECK(out); bool equal; CHECK(JS_StringEqualsAscii(cx, out, *s, &equal)); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 3901a0e63c..d16df26ba7 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4438,7 +4438,7 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, } JS_PUBLIC_API(JSString*) -JS_DecompileScript(JSContext* cx, HandleScript script, const char* name, unsigned indent) +JS_DecompileScript(JSContext* cx, HandleScript script) { MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); @@ -4447,7 +4447,7 @@ JS_DecompileScript(JSContext* cx, HandleScript script, const char* name, unsigne script->ensureNonLazyCanonicalFunction(); RootedFunction fun(cx, script->functionNonDelazifying()); if (fun) - return JS_DecompileFunction(cx, fun, indent); + return JS_DecompileFunction(cx, fun); bool haveSource = script->scriptSource()->hasSourceData(); if (!haveSource && !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) return nullptr; @@ -4456,13 +4456,13 @@ JS_DecompileScript(JSContext* cx, HandleScript script, const char* name, unsigne } JS_PUBLIC_API(JSString*) -JS_DecompileFunction(JSContext* cx, HandleFunction fun, unsigned indent) +JS_DecompileFunction(JSContext* cx, HandleFunction fun) { MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, fun); - return FunctionToString(cx, fun, !(indent & JS_DONT_PRETTY_PRINT)); + return FunctionToString(cx, fun, /* isToSource = */ false); } MOZ_NEVER_INLINE static bool diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 8e70cc152b..0b865ff523 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4200,16 +4200,10 @@ CompileFunction(JSContext* cx, AutoObjectVector& envChain, } /* namespace JS */ extern JS_PUBLIC_API(JSString*) -JS_DecompileScript(JSContext* cx, JS::Handle script, const char* name, unsigned indent); - -/* - * API extension: OR this into indent to avoid pretty-printing the decompiled - * source resulting from JS_DecompileFunction. - */ -#define JS_DONT_PRETTY_PRINT ((unsigned)0x8000) +JS_DecompileScript(JSContext* cx, JS::Handle script); extern JS_PUBLIC_API(JSString*) -JS_DecompileFunction(JSContext* cx, JS::Handle fun, unsigned indent); +JS_DecompileFunction(JSContext* cx, JS::Handle fun); /* diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 494bcb042a..00da8295ad 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -377,7 +377,7 @@ extern JS_FRIEND_API(bool) proxy_GetElements(JSContext* cx, JS::HandleObject proxy, uint32_t begin, uint32_t end, ElementAdder* adder); extern JS_FRIEND_API(JSString*) -proxy_FunToString(JSContext* cx, JS::HandleObject proxy, unsigned indent); +proxy_FunToString(JSContext* cx, JS::HandleObject proxy, bool isToSource); /** * A class of objects that return source code on demand. diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index b4aeee51c2..fc243fab66 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -942,24 +942,24 @@ const Class JSFunction::class_ = { const Class* const js::FunctionClassPtr = &JSFunction::class_; JSString* -js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) +js::FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource) { if (fun->isInterpretedLazy() && !JSFunction::getOrCreateScript(cx, fun)) return nullptr; if (IsAsmJSModule(fun)) - return AsmJSModuleToString(cx, fun, !prettyPrint); + return AsmJSModuleToString(cx, fun, isToSource); if (IsAsmJSFunction(fun)) return AsmJSFunctionToString(cx, fun); if (IsWrappedAsyncFunction(fun)) { RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun)); - return FunctionToString(cx, unwrapped, prettyPrint); + return FunctionToString(cx, unwrapped, isToSource); } if (IsWrappedAsyncGenerator(fun)) { RootedFunction unwrapped(cx, GetUnwrappedAsyncGenerator(fun)); - return FunctionToString(cx, unwrapped, prettyPrint); + return FunctionToString(cx, unwrapped, isToSource); } StringBuffer out(cx); @@ -985,9 +985,9 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) bool haveSource = fun->isInterpreted() && (fun->isClassConstructor() || !fun->isSelfHostedBuiltin()); - // If we're not in pretty mode, put parentheses around lambda functions - // so that eval returns lambda, not function statement. - bool addParentheses = haveSource && !prettyPrint && (fun->isLambda() && !fun->isArrow()); + // If we're in toSource mode, put parentheses around lambda functions so + // that eval returns lambda, not function statement. + bool addParentheses = haveSource && isToSource && (fun->isLambda() && !fun->isArrow()); if (haveSource && !script->scriptSource()->hasSourceData() && !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) @@ -1066,11 +1066,11 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) } JSString* -fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent) +fun_toStringHelper(JSContext* cx, HandleObject obj, bool isToSource) { if (!obj->is()) { if (JSFunToStringOp op = obj->getOpsFunToString()) - return op(cx, obj, indent); + return op(cx, obj, isToSource); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, @@ -1079,7 +1079,7 @@ fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent) } RootedFunction fun(cx, &obj->as()); - return FunctionToString(cx, fun, indent != JS_DONT_PRETTY_PRINT); + return FunctionToString(cx, fun, isToSource); } bool @@ -1102,16 +1102,11 @@ js::fun_toString(JSContext* cx, unsigned argc, Value* vp) CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(IsFunctionObject(args.calleev())); - uint32_t indent = 0; - - if (args.length() != 0 && !ToUint32(cx, args[0], &indent)) - return false; - RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; - RootedString str(cx, fun_toStringHelper(cx, obj, indent)); + JSString* str = fun_toStringHelper(cx, obj, /* isToSource = */ false); if (!str) return false; @@ -1132,12 +1127,12 @@ fun_toSource(JSContext* cx, unsigned argc, Value* vp) RootedString str(cx); if (obj->isCallable()) - str = fun_toStringHelper(cx, obj, JS_DONT_PRETTY_PRINT); + str = fun_toStringHelper(cx, obj, /* isToSource = */ true); else str = ObjectToSource(cx, obj); - if (!str) return false; + args.rval().setString(str); return true; } diff --git a/js/src/jsfun.h b/js/src/jsfun.h index ecdc2f2ebd..481f062080 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -670,7 +670,7 @@ static_assert(sizeof(JSFunction) == sizeof(js::shadow::Function), "shadow interface must match actual interface"); extern JSString* -fun_toStringHelper(JSContext* cx, js::HandleObject obj, unsigned indent); +fun_toStringHelper(JSContext* cx, js::HandleObject obj, bool isToSource); namespace js { @@ -875,7 +875,7 @@ JSFunction::getExtendedSlot(size_t which) const namespace js { -JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPring); +JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource); template bool diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 982ba8e7f0..30fcd0c87a 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -863,7 +863,7 @@ ToDisassemblySource(JSContext* cx, HandleValue v, JSAutoByteString* bytes) if (obj.is()) { RootedFunction fun(cx, &obj.as()); - JSString* str = JS_DecompileFunction(cx, fun, JS_DONT_PRETTY_PRINT); + JSString* str = JS_DecompileFunction(cx, fun); if (!str) return false; return bytes->encodeLatin1(cx, str); @@ -1972,7 +1972,7 @@ GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, StringBuffer& buf) if (!AppendJSONProperty(buf, "text", NO_COMMA)) return false; - JSString* str = JS_DecompileScript(cx, script, nullptr, 0); + JSString* str = JS_DecompileScript(cx, script); if (!str || !(str = StringToSource(cx, str))) return false; diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index b7bfef1c37..3729ba884d 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -115,7 +115,7 @@ class JS_FRIEND_API(Wrapper) : public BaseProxyHandler JS::IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, - unsigned indent) const override; + bool isToSource) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, @@ -210,7 +210,7 @@ class JS_FRIEND_API(CrossCompartmentWrapper) : public Wrapper bool* bp) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject wrapper, - unsigned indent) const override; + bool isToSource) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override; @@ -272,7 +272,8 @@ class JS_FRIEND_API(OpaqueCrossCompartmentWrapper) : public CrossCompartmentWrap virtual bool hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v, bool* bp) const override; virtual const char* className(JSContext* cx, HandleObject wrapper) const override; - virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override; + virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, + bool isToSource) const override; static const OpaqueCrossCompartmentWrapper singleton; }; diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp index c433e7ace5..516ecd79d8 100644 --- a/js/src/proxy/BaseProxyHandler.cpp +++ b/js/src/proxy/BaseProxyHandler.cpp @@ -315,7 +315,7 @@ BaseProxyHandler::className(JSContext* cx, HandleObject proxy) const } JSString* -BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const +BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const { if (proxy->isCallable()) return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}"); diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp index cb2111778d..e12f96bfa7 100644 --- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -422,12 +422,12 @@ CrossCompartmentWrapper::className(JSContext* cx, HandleObject wrapper) const } JSString* -CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, unsigned indent) const +CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, bool isToSource) const { RootedString str(cx); { AutoCompartment call(cx, wrappedObject(wrapper)); - str = Wrapper::fun_toString(cx, wrapper, indent); + str = Wrapper::fun_toString(cx, wrapper, isToSource); if (!str) return nullptr; } diff --git a/js/src/proxy/DeadObjectProxy.cpp b/js/src/proxy/DeadObjectProxy.cpp index 95869fa03a..34d9c3b04d 100644 --- a/js/src/proxy/DeadObjectProxy.cpp +++ b/js/src/proxy/DeadObjectProxy.cpp @@ -135,7 +135,7 @@ DeadObjectProxy::className(JSContext* cx, HandleObject wrapper) const } JSString* -DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const +DeadObjectProxy::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const { ReportDead(cx); return nullptr; diff --git a/js/src/proxy/DeadObjectProxy.h b/js/src/proxy/DeadObjectProxy.h index 0715625fec..bedec9c040 100644 --- a/js/src/proxy/DeadObjectProxy.h +++ b/js/src/proxy/DeadObjectProxy.h @@ -47,7 +47,8 @@ class DeadObjectProxy : public BaseProxyHandler virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const override; virtual bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; - virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const override; + virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, + bool isToSource) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; virtual bool isCallable(JSObject* obj) const override; diff --git a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp index 84b42a7cd5..b63e5cc7ab 100644 --- a/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp +++ b/js/src/proxy/OpaqueCrossCompartmentWrapper.cpp @@ -191,7 +191,7 @@ OpaqueCrossCompartmentWrapper::className(JSContext* cx, JSString* OpaqueCrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject proxy, - unsigned indent) const + bool isToSource) const { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, js_Function_str, js_toString_str, "object"); diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index 6f91dfb106..d873717248 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -475,7 +475,7 @@ Proxy::className(JSContext* cx, HandleObject proxy) } JSString* -Proxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) +Proxy::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) { JS_CHECK_RECURSION(cx, return nullptr); const BaseProxyHandler* handler = proxy->as().handler(); @@ -483,8 +483,8 @@ Proxy::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) BaseProxyHandler::GET, /* mayThrow = */ false); // Do the safe thing if the policy rejects. if (!policy.allowed()) - return handler->BaseProxyHandler::fun_toString(cx, proxy, indent); - return handler->fun_toString(cx, proxy, indent); + return handler->BaseProxyHandler::fun_toString(cx, proxy, isToSource); + return handler->fun_toString(cx, proxy, isToSource); } bool @@ -691,9 +691,9 @@ js::proxy_GetElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_ } JSString* -js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent) +js::proxy_FunToString(JSContext* cx, HandleObject proxy, bool isToSource) { - return Proxy::fun_toString(cx, proxy, indent); + return Proxy::fun_toString(cx, proxy, isToSource); } const ClassOps js::ProxyClassOps = { diff --git a/js/src/proxy/Proxy.h b/js/src/proxy/Proxy.h index 1b8e368c3b..a1942b25de 100644 --- a/js/src/proxy/Proxy.h +++ b/js/src/proxy/Proxy.h @@ -60,7 +60,7 @@ class Proxy static bool getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls); static bool isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer); static const char* className(JSContext* cx, HandleObject proxy); - static JSString* fun_toString(JSContext* cx, HandleObject proxy, unsigned indent); + static JSString* fun_toString(JSContext* cx, HandleObject proxy, bool isToSource); static bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g); static bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp); diff --git a/js/src/proxy/ScriptedProxyHandler.cpp b/js/src/proxy/ScriptedProxyHandler.cpp index ebbc223827..19d4b67850 100644 --- a/js/src/proxy/ScriptedProxyHandler.cpp +++ b/js/src/proxy/ScriptedProxyHandler.cpp @@ -1257,7 +1257,7 @@ ScriptedProxyHandler::className(JSContext* cx, HandleObject proxy) const } JSString* -ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const +ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, js_Function_str, js_toString_str, "object"); diff --git a/js/src/proxy/ScriptedProxyHandler.h b/js/src/proxy/ScriptedProxyHandler.h index 8ce0119185..e464dc24e8 100644 --- a/js/src/proxy/ScriptedProxyHandler.h +++ b/js/src/proxy/ScriptedProxyHandler.h @@ -67,7 +67,7 @@ class ScriptedProxyHandler : public BaseProxyHandler JS::IsArrayAnswer* answer) const override; virtual const char* className(JSContext* cx, HandleObject proxy) const override; virtual JSString* fun_toString(JSContext* cx, HandleObject proxy, - unsigned indent) const override; + bool isToSource) const override; virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp index bb69123a59..36cb1317f7 100644 --- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -259,11 +259,11 @@ Wrapper::className(JSContext* cx, HandleObject proxy) const } JSString* -Wrapper::fun_toString(JSContext* cx, HandleObject proxy, unsigned indent) const +Wrapper::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const { assertEnteredPolicy(cx, proxy, JSID_VOID, GET); RootedObject target(cx, proxy->as().target()); - return fun_toStringHelper(cx, target, indent); + return fun_toStringHelper(cx, target, isToSource); } bool diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 755fb73f1a..749e7a79a8 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4577,7 +4577,7 @@ DecompileFunction(JSContext* cx, unsigned argc, Value* vp) return true; } RootedFunction fun(cx, &args[0].toObject().as()); - JSString* result = JS_DecompileFunction(cx, fun, 0); + JSString* result = JS_DecompileFunction(cx, fun); if (!result) return false; args.rval().setString(result); @@ -4599,7 +4599,7 @@ DecompileThisScript(JSContext* cx, unsigned argc, Value* vp) JSAutoCompartment ac(cx, iter.script()); RootedScript script(cx, iter.script()); - JSString* result = JS_DecompileScript(cx, script, "test", 0); + JSString* result = JS_DecompileScript(cx, script); if (!result) return false; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index f659c27ffa..0ecaee6d45 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -8849,7 +8849,7 @@ js::IsAsmJSModuleLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) // asm.js toString/toSource support JSString* -js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda) +js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool isToSource) { MOZ_ASSERT(IsAsmJSModule(fun)); @@ -8860,7 +8860,7 @@ js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda StringBuffer out(cx); - if (addParenToLambda && fun->isLambda() && !out.append("(")) + if (isToSource && fun->isLambda() && !out.append("(")) return nullptr; bool haveSource = source->hasSourceData(); @@ -8883,7 +8883,7 @@ js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda return nullptr; } - if (addParenToLambda && fun->isLambda() && !out.append(")")) + if (isToSource && fun->isLambda() && !out.append(")")) return nullptr; return out.finishString(); diff --git a/js/src/wasm/AsmJS.h b/js/src/wasm/AsmJS.h index 15d39991f7..a38b204a8b 100644 --- a/js/src/wasm/AsmJS.h +++ b/js/src/wasm/AsmJS.h @@ -76,7 +76,7 @@ extern JSString* AsmJSFunctionToString(JSContext* cx, HandleFunction fun); extern JSString* -AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda); +AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool isToSource); // asm.js heap: From 887e6d0ab313bb0801de8ff32a758c181ed0c179 Mon Sep 17 00:00:00 2001 From: Martok Date: Fri, 1 Jul 2022 17:36:42 +0200 Subject: [PATCH 6/6] Issue #1952 - m-c 1440468: Proxied functions can't be passed to Function.prototype.toString.call() --- js/src/jit-test/tests/basic/bug807623.js | 7 +------ js/src/jit-test/tests/proxy/function-toString.js | 8 ++++---- js/src/proxy/BaseProxyHandler.cpp | 5 +++-- js/src/proxy/ScriptedProxyHandler.cpp | 6 +++--- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/js/src/jit-test/tests/basic/bug807623.js b/js/src/jit-test/tests/basic/bug807623.js index ce16ffd417..35c34d3cb3 100644 --- a/js/src/jit-test/tests/basic/bug807623.js +++ b/js/src/jit-test/tests/basic/bug807623.js @@ -3,12 +3,7 @@ var functionProxy = new Proxy(function() {}, {}); assertEq(Object.prototype.toString.call(objectProxy), '[object Object]'); assertEq(Object.prototype.toString.call(functionProxy), '[object Function]'); -try { - Function.prototype.toString.call(functionProxy); - assertEq(true, false); -} catch (e) { - assertEq(!!/incompatible/.exec(e), true); -} + try { Function.prototype.toString.call(objectProxy); assertEq(true, false); diff --git a/js/src/jit-test/tests/proxy/function-toString.js b/js/src/jit-test/tests/proxy/function-toString.js index cedcf552a4..4aca5c907e 100644 --- a/js/src/jit-test/tests/proxy/function-toString.js +++ b/js/src/jit-test/tests/proxy/function-toString.js @@ -1,10 +1,10 @@ load(libdir + 'asserts.js'); -// Function.prototype.toString doesn't accept ES6 proxies. +var nativeCode = "function () {\n [native code]\n}"; var proxy = new Proxy(function() {}, {}); -assertThrowsInstanceOf(() => Function.prototype.toString.call(proxy), TypeError); +assertEq(Function.prototype.toString.call(proxy), nativeCode); var o = Proxy.revocable(function() {}, {}); -assertThrowsInstanceOf(() => Function.prototype.toString.call(o.proxy), TypeError); +assertEq(Function.prototype.toString.call(o.proxy), nativeCode); o.revoke(); -assertThrowsInstanceOf(() => Function.prototype.toString.call(o.proxy), TypeError); +assertEq(Function.prototype.toString.call(o.proxy), nativeCode); diff --git a/js/src/proxy/BaseProxyHandler.cpp b/js/src/proxy/BaseProxyHandler.cpp index 516ecd79d8..6cd526d059 100644 --- a/js/src/proxy/BaseProxyHandler.cpp +++ b/js/src/proxy/BaseProxyHandler.cpp @@ -319,8 +319,9 @@ BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSourc { if (proxy->isCallable()) return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}"); - RootedValue v(cx, ObjectValue(*proxy)); - ReportIsNotFunction(cx, v); + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, + js_Function_str, js_toString_str, "object"); return nullptr; } diff --git a/js/src/proxy/ScriptedProxyHandler.cpp b/js/src/proxy/ScriptedProxyHandler.cpp index 19d4b67850..5a219e2e3f 100644 --- a/js/src/proxy/ScriptedProxyHandler.cpp +++ b/js/src/proxy/ScriptedProxyHandler.cpp @@ -1259,9 +1259,9 @@ ScriptedProxyHandler::className(JSContext* cx, HandleObject proxy) const JSString* ScriptedProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, - js_Function_str, js_toString_str, "object"); - return nullptr; + // The BaseProxyHandler has the desired behavior: Throw for non-callable, + // otherwise return [native code]. + return BaseProxyHandler::fun_toString(cx, proxy, isToSource); } bool