diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-03-20 10:08:54 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-03-20 10:10:12 +0100 |
commit | 893a886ea38853a1a3e97bcf135ea3cb616cd69a (patch) | |
tree | 5188f8895ce513381917d37115b50f72fb4e64a9 | |
parent | 7197b308fb97cd8ab7a972df6a3a17a7a265b594 (diff) | |
parent | 6085bfdcecc2529c1037f813e70583c2a776677d (diff) | |
download | UXP-893a886ea38853a1a3e97bcf135ea3cb616cd69a.tar UXP-893a886ea38853a1a3e97bcf135ea3cb616cd69a.tar.gz UXP-893a886ea38853a1a3e97bcf135ea3cb616cd69a.tar.lz UXP-893a886ea38853a1a3e97bcf135ea3cb616cd69a.tar.xz UXP-893a886ea38853a1a3e97bcf135ea3cb616cd69a.zip |
Add support for the function `name` property.
This resolves #78.
Merged remote-tracking branch 'janek/js_function_name_1'
67 files changed, 1085 insertions, 222 deletions
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js index 739d3b2a7..b2fa66872 100644 --- a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js +++ b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js @@ -53,13 +53,13 @@ function test() { .getAttribute("value"), "getName", "Should have the right property name for 'getName' in person."); is(personNode.get("getName").target.querySelector(".value") - .getAttribute("value"), "_pfactory/<.getName()", + .getAttribute("value"), "getName()", "'getName' in person should have the right value."); is(personNode.get("getFoo").target.querySelector(".name") .getAttribute("value"), "getFoo", "Should have the right property name for 'getFoo' in person."); is(personNode.get("getFoo").target.querySelector(".value") - .getAttribute("value"), "_pfactory/<.getFoo()", + .getAttribute("value"), "getFoo()", "'getFoo' in person should have the right value."); // Expand the function nodes. This causes their properties to be diff --git a/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js b/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js index 6a29d61aa..769fc0fe5 100644 --- a/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js +++ b/devtools/client/webconsole/test/browser_webconsole_closure_inspection.js @@ -58,7 +58,7 @@ function consoleOpened(hud) { waitForMessages({ webconsole: gWebConsole, messages: [{ - text: "function _pfactory/<.getName()", + text: "getName()", category: CATEGORY_OUTPUT, objects: true, }], diff --git a/devtools/client/webconsole/test/browser_webconsole_output_02.js b/devtools/client/webconsole/test/browser_webconsole_output_02.js index 8018669a9..4c61cf041 100644 --- a/devtools/client/webconsole/test/browser_webconsole_output_02.js +++ b/devtools/client/webconsole/test/browser_webconsole_output_02.js @@ -36,10 +36,10 @@ var inputTests = [ suppressClick: true }, - // 3 - anonymous function, but spidermonkey gives us an inferred name. + // 3 - anonymous function, but gets name. { input: "testobj1.testfn2", - output: "function testobj1.testfn2()", + output: "function testfn2()", printOutput: "function () { return 42; }", suppressClick: true }, diff --git a/devtools/server/event-parsers.js b/devtools/server/event-parsers.js index a813d8e9b..6245af190 100644 --- a/devtools/server/event-parsers.js +++ b/devtools/server/event-parsers.js @@ -91,44 +91,58 @@ var parsers = [ return jQueryLiveGetListeners(node, false); }, normalizeHandler: function (handlerDO) { - let paths = [ - [".event.proxy/", ".event.proxy/", "*"], - [".proxy/", "*"] - ]; - - let name = handlerDO.displayName; + function isFunctionInProxy(funcDO) { + // If the anonymous function is inside the |proxy| function and the + // function only has guessed atom, the guessed atom should starts with + // "proxy/". + let displayName = funcDO.displayName; + if (displayName && displayName.startsWith("proxy/")) { + return true; + } - if (!name) { - return handlerDO; + // If the anonymous function is inside the |proxy| function and the + // function gets name at compile time by SetFunctionName, its guessed + // atom doesn't contain "proxy/". In that case, check if the caller is + // "proxy" function, as a fallback. + let calleeDO = funcDO.environment.callee; + if (!calleeDO) { + return false; + } + let calleeName = calleeDO.displayName; + return calleeName == "proxy"; } - for (let path of paths) { - if (name.includes(path[0])) { - path.splice(0, 1); - - for (let point of path) { - let names = handlerDO.environment.names(); + function getFirstFunctionVariable(funcDO) { + // The handler function inside the |proxy| function should point the + // unwrapped function via environment variable. + let names = funcDO.environment.names(); + for (let varName of names) { + let varDO = handlerDO.environment.getVariable(varName); + if (!varDO) { + continue; + } + if (varDO.class == "Function") { + return varDO; + } + } + return null; + } - for (let varName of names) { - let temp = handlerDO.environment.getVariable(varName); - if (!temp) { - continue; - } + if (!isFunctionInProxy(handlerDO)) { + return handlerDO; + } - let displayName = temp.displayName; - if (!displayName) { - continue; - } + const MAX_NESTED_HANDLER_COUNT = 2; + for (let i = 0; i < MAX_NESTED_HANDLER_COUNT; i++) { + let funcDO = getFirstFunctionVariable(handlerDO); + if (!funcDO) + return handlerDO; - if (temp.class === "Function" && - (displayName.includes(point) || point === "*")) { - handlerDO = temp; - break; - } - } - } - break; + handlerDO = funcDO; + if (isFunctionInProxy(handlerDO)) { + continue; } + break; } return handlerDO; diff --git a/devtools/server/tests/unit/test_functiongrips-01.js b/devtools/server/tests/unit/test_functiongrips-01.js index c41a7cad5..81b1b7767 100644 --- a/devtools/server/tests/unit/test_functiongrips-01.js +++ b/devtools/server/tests/unit/test_functiongrips-01.js @@ -52,7 +52,7 @@ function test_inferred_name_function() { do_check_eq(args[0].class, "Function"); // No name for an anonymous function, but it should have an inferred name. do_check_eq(args[0].name, undefined); - do_check_eq(args[0].displayName, "o.m"); + do_check_eq(args[0].displayName, "m"); let objClient = gThreadClient.pauseGrip(args[0]); objClient.getParameterNames(function (aResponse) { diff --git a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html index 5428030c5..68de079ed 100644 --- a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html +++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html @@ -93,7 +93,7 @@ doTest@${ourFile}:56:7 checkExn.bind(null, 90, "ReferenceError", "thereIsNoSuchContentFunction3 is not defined", undefined, ourFile, 6, - `doTest/<.then@${ourFile}:90:32 + `then@${ourFile}:90:32 ` + (asyncStack ? `Async*doTest@${ourFile}:89:7\n` + parentFrame : ""))), t.testPromiseWithDOMExceptionThrowingPromiseInit().then( ensurePromiseFail.bind(null, 7), diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 3bfc8f60b..40fd008b9 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -1205,7 +1205,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) case PNK_FUNCTION: { RootedFunction func(cx_, kid->pn_funbox->function()); if (!func->isArrow()) { - RootedAtom localName(cx_, func->name()); + RootedAtom localName(cx_, func->explicitName()); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); MOZ_ASSERT_IF(isDefault, localName); if (!appendExportEntry(exportName, localName)) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 748ff7351..eef6fd7ec 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3415,7 +3415,7 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) #endif RootedValue id(cx); - RootedAtom funcAtom(cx, func->name()); + RootedAtom funcAtom(cx, func->explicitName()); if (!optIdentifier(funcAtom, nullptr, &id)) return false; @@ -3423,7 +3423,7 @@ ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst) NodeVector defaults(cx); RootedValue body(cx), rest(cx); - if (func->hasRest()) + if (pn->pn_funbox->hasRest()) rest.setUndefined(); else rest.setNull(); diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index b7297c894..ae74f01bf 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -230,7 +230,7 @@ const Class js::ScalarTypeDescr::class_ = { const JSFunctionSpec js::ScalarTypeDescr::typeObjectMethods[] = { JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), - JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, JSFUN_HAS_REST), + JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0), JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0), JS_FS_END }; diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 3fbfdaa1a..76afe80b1 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -336,10 +336,10 @@ BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc) if (!deoptimizeArgumentsInEnclosingScripts(cx->asJSContext(), environment)) return nullptr; } - if (!NameFunctions(cx, pn)) - return nullptr; if (!emitter->emitScript(pn)) return nullptr; + if (!NameFunctions(cx, pn)) + return nullptr; parser->handler.freeTree(pn); break; @@ -397,15 +397,15 @@ BytecodeCompiler::compileModule() if (!pn) return nullptr; - if (!NameFunctions(cx, pn)) - return nullptr; - Maybe<BytecodeEmitter> emitter; if (!emplaceEmitter(emitter, &modulesc)) return nullptr; if (!emitter->emitScript(pn->pn_body)) return nullptr; + if (!NameFunctions(cx, pn)) + return nullptr; + parser->handler.freeTree(pn); if (!builder.initModule()) @@ -453,9 +453,6 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, return false; } while (!fn); - if (!NameFunctions(cx, fn)) - return false; - if (fn->pn_funbox->function()->isInterpreted()) { MOZ_ASSERT(fun == fn->pn_funbox->function()); @@ -472,6 +469,9 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, MOZ_ASSERT(IsAsmJSModule(fun)); } + if (!NameFunctions(cx, fn)) + return false; + if (!maybeCompleteCompressSource()) return false; @@ -646,9 +646,6 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha if (!pn) return false; - if (!NameFunctions(cx, pn)) - return false; - RootedScriptSource sourceObject(cx, lazy->sourceObject()); MOZ_ASSERT(sourceObject); @@ -667,7 +664,13 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha if (!bce.init()) return false; - return bce.emitFunctionScript(pn->pn_body); + if (!bce.emitFunctionScript(pn->pn_body)) + return false; + + if (!NameFunctions(cx, pn)) + return false; + + return true; } bool diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1e9d8f224..acf734794 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1053,7 +1053,7 @@ BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* if (p) { MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); MOZ_ASSERT(!funbox->hasDestructuringArgs); - MOZ_ASSERT(!funbox->function()->hasRest()); + MOZ_ASSERT(!funbox->hasRest()); p->value() = loc; continue; } @@ -4070,7 +4070,7 @@ BytecodeEmitter::isRunOnceLambda() FunctionBox* funbox = sc->asFunctionBox(); return !funbox->argumentsHasLocalBinding() && !funbox->isGenerator() && - !funbox->function()->name(); + !funbox->function()->explicitName(); } bool @@ -4463,7 +4463,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } bool -BytecodeEmitter::emitConditionallyExecutedDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +BytecodeEmitter::emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav) { TDZCheckCache tdzCache(this); return emitDestructuringLHS(target, flav); @@ -4491,7 +4491,7 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool -BytecodeEmitter::emitDefault(ParseNode* defaultExpr) +BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { if (!emit1(JSOP_DUP)) // VALUE VALUE return false; @@ -4507,13 +4507,77 @@ BytecodeEmitter::emitDefault(ParseNode* defaultExpr) return false; if (!emit1(JSOP_POP)) // . return false; - if (!emitConditionallyExecutedTree(defaultExpr)) // DEFAULTVALUE + if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE return false; if (!emitJumpTargetAndPatch(jump)) return false; return true; } +bool +BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, + FunctionPrefixKind prefixKind) +{ + if (maybeFun->isKind(PNK_FUNCTION)) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + RootedFunction fun(cx, maybeFun->pn_funbox->function()); + + // Single node can be emitted multiple times if it appears in + // array destructuring default. If function already has a name, + // just return. + if (fun->hasCompileTimeName()) { +#ifdef DEBUG + RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + if (!funName) + return false; + MOZ_ASSERT(funName == maybeFun->pn_funbox->function()->compileTimeName()); +#endif + return true; + } + + RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + if (!funName) + return false; + fun->setCompileTimeName(name); + return true; + } + + uint32_t nameIndex; + if (!makeAtomIndex(name, &nameIndex)) + return false; + if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME + return false; + uint8_t kind = uint8_t(prefixKind); + if (!emit2(JSOP_SETFUNNAME, kind)) // FUN + return false; + return true; +} + +bool +BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) +{ + if (!emitTree(initializer)) + return false; + + if (!pattern->isInParens() && pattern->isKind(PNK_NAME) && + initializer->isDirectRHSAnonFunction()) + { + RootedAtom name(cx, pattern->name()); + if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None)) + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) +{ + TDZCheckCache tdzCache(this); + return emitInitializer(initializer, pattern); +} + class MOZ_STACK_CLASS IfThenElseEmitter { BytecodeEmitter* bce_; @@ -4765,7 +4829,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY return false; - if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? return false; if (!ifThenElse.emitElse()) // ... OBJ? ITER @@ -4782,7 +4846,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (!emit1(JSOP_POP)) // ... OBJ? ARRAY return false; - if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? return false; if (!isHead) { @@ -4834,7 +4898,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav if (pndefault) { // Emit only pndefault tree here, as undefined check in emitDefault // should always be true. - if (!emitConditionallyExecutedTree(pndefault)) // ... OBJ? ITER VALUE + if (!emitInitializerInBranch(pndefault, subpattern)) // ... OBJ? ITER VALUE return false; } else { if (!isElision) { @@ -4845,7 +4909,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } } if (!isElision) { - if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER + if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER return false; } else if (pndefault) { if (!emit1(JSOP_POP)) // ... OBJ? ITER @@ -4872,12 +4936,12 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav return false; if (pndefault) { - if (!emitDefault(pndefault)) // ... OBJ? ITER VALUE + if (!emitDefault(pndefault, subpattern)) // ... OBJ? ITER VALUE return false; } if (!isElision) { - if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER + if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER return false; } else { if (!emit1(JSOP_POP)) // ... OBJ? ITER @@ -4980,7 +5044,7 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right)) + if (!emitDefault(subpattern->pn_right, subpattern->pn_left)) return false; subpattern = subpattern->pn_left; } @@ -5094,7 +5158,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, if (!initializer && declList->isKind(PNK_VAR)) return true; - auto emitRhs = [initializer, declList](BytecodeEmitter* bce, const NameLocation&, bool) { + auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) { if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. @@ -5105,7 +5169,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, } MOZ_ASSERT(initializer); - return bce->emitTree(initializer); + return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) @@ -5164,6 +5228,12 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs) if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) return false; + if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) { + RootedAtom name(bce->cx, lhs->name()); + if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None)) + return false; + } + // Emit the compound assignment op if there is one. if (op != JSOP_NOP && !bce->emit1(op)) return false; @@ -5788,7 +5858,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) if_again: /* Emit code for the condition before pushing stmtInfo. */ - if (!emitConditionallyExecutedTree(pn->pn_kid1)) + if (!emitTreeInBranch(pn->pn_kid1)) return false; ParseNode* elseNode = pn->pn_kid3; @@ -5801,7 +5871,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) } /* Emit code for the then part. */ - if (!emitConditionallyExecutedTree(pn->pn_kid2)) + if (!emitTreeInBranch(pn->pn_kid2)) return false; if (elseNode) { @@ -5814,7 +5884,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) } /* Emit code for the else part. */ - if (!emitConditionallyExecutedTree(elseNode)) + if (!emitTreeInBranch(elseNode)) return false; } @@ -6283,8 +6353,8 @@ BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitte if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false; - auto emitRhs = [initializer](BytecodeEmitter* bce, const NameLocation&, bool) { - return bce->emitTree(initializer); + auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) { + return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) @@ -6504,7 +6574,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp)) return false; - if (!emitConditionallyExecutedTree(forBody)) + if (!emitTreeInBranch(forBody)) return false; // Set loop and enclosing "update" offsets, for continue. Note that we @@ -6903,7 +6973,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) { FunctionBox* funbox = pn->pn_funbox; RootedFunction fun(cx, funbox->function()); - RootedAtom name(cx, fun->name()); + RootedAtom name(cx, fun->explicitName()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto); @@ -7284,7 +7354,7 @@ BytecodeEmitter::emitWhile(ParseNode* pn) if (!emitLoopHead(pn->pn_right, &top)) return false; - if (!emitConditionallyExecutedTree(pn->pn_right)) + if (!emitTreeInBranch(pn->pn_right)) return false; if (!emitLoopEntry(pn->pn_left, jmp)) @@ -8017,7 +8087,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); - if (!fun->hasRest()) { + if (!funbox->hasRest()) { *result = false; return true; } @@ -8449,13 +8519,13 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) if (!ifThenElse.emitCond()) return false; - if (!emitConditionallyExecutedTree(&conditional.thenExpression())) + if (!emitTreeInBranch(&conditional.thenExpression())) return false; if (!ifThenElse.emitElse()) return false; - if (!emitConditionallyExecutedTree(&conditional.elseExpression())) + if (!emitTreeInBranch(&conditional.elseExpression())) return false; if (!ifThenElse.emitEnd()) @@ -8532,6 +8602,10 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); + FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get + : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set + : FunctionPrefixKind::None; + if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) objp.set(nullptr); @@ -8573,6 +8647,12 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; default: MOZ_CRASH("Invalid op"); } + if (propdef->pn_right->isDirectRHSAnonFunction()) { + if (!emitDupAt(1)) + return false; + if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) + return false; + } if (!emit1(op)) return false; } else { @@ -8597,6 +8677,11 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, objp.set(nullptr); } + if (propdef->pn_right->isDirectRHSAnonFunction()) { + RootedAtom keyName(cx, key->pn_atom); + if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind)) + return false; + } if (!emitIndex32(op, index)) return false; } @@ -8960,7 +9045,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) EmitterScope* funScope = innermostEmitterScope; bool hasParameterExprs = funbox->hasParameterExprs; - bool hasRest = funbox->function()->hasRest(); + bool hasRest = funbox->hasRest(); uint16_t argSlot = 0; for (ParseNode* arg = pn->pn_head; arg != funBody; arg = arg->pn_next, argSlot++) { @@ -9017,7 +9102,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) return false; if (!emit1(JSOP_POP)) return false; - if (!emitConditionallyExecutedTree(initializer)) + if (!emitInitializerInBranch(initializer, bindingElement)) return false; if (!emitJumpTargetAndPatch(jump)) return false; @@ -9728,7 +9813,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) } bool -BytecodeEmitter::emitConditionallyExecutedTree(ParseNode* pn) +BytecodeEmitter::emitTreeInBranch(ParseNode* pn) { // Code that may be conditionally executed always need their own TDZ // cache. diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 1bb4191ee..9a2ddb568 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -435,7 +435,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); // Emit code for the tree rooted at pn with its own TDZ cache. - MOZ_MUST_USE bool emitConditionallyExecutedTree(ParseNode* pn); + MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn); // Emit global, eval, or module code for tree rooted at body. Always // encompasses the entire source. @@ -648,8 +648,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // the stack and emits code to destructure a single lhs expression (either a // name or a compound []/{} expression). MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); - MOZ_MUST_USE bool emitConditionallyExecutedDestructuringLHS(ParseNode* target, - DestructuringFlavor flav); + MOZ_MUST_USE bool emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or @@ -678,7 +677,16 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Check if the value on top of the stack is "undefined". If so, replace // that value on the stack with the value defined by |defaultExpr|. - MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr); + // |pattern| is a lhs node of the default expression. If it's an + // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| + // is called at compile time. + MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, + FunctionPrefixKind prefixKind); + + MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); + MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn); MOZ_MUST_USE bool emitTemplateString(ParseNode* pn); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index add881900..0fd137796 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -665,6 +665,11 @@ class FullParseHandler ParseNode* pn); inline void setLastFunctionFormalParameterDestructuring(ParseNode* funcpn, ParseNode* pn); + void checkAndSetIsDirectRHSAnonFunction(ParseNode* pn) { + if (IsAnonymousFunctionDefinition(pn)) + pn->setDirectRHSAnonFunction(true); + } + ParseNode* newFunctionDefinition() { return new_<CodeNode>(PNK_FUNCTION, pos()); } @@ -942,6 +947,8 @@ FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, Parse if (!pn) return false; + checkAndSetIsDirectRHSAnonFunction(defaultValue); + funcpn->pn_body->pn_pos.end = pn->pn_pos.end; ParseNode* pnchild = funcpn->pn_body->pn_head; ParseNode* pnlast = funcpn->pn_body->last(); diff --git a/js/src/frontend/ParseNode-inl.h b/js/src/frontend/ParseNode-inl.h index 395d09b5b..21bd83766 100644 --- a/js/src/frontend/ParseNode-inl.h +++ b/js/src/frontend/ParseNode-inl.h @@ -18,7 +18,7 @@ inline PropertyName* ParseNode::name() const { MOZ_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME)); - JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->name() : pn_atom; + JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->explicitName() : pn_atom; return atom->asPropertyName(); } diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index f79baba9e..ece3a45df 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -902,3 +902,21 @@ FunctionBox::trace(JSTracer* trc) if (enclosingScope_) TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope"); } + +bool +js::frontend::IsAnonymousFunctionDefinition(ParseNode* pn) +{ + // ES 2017 draft + // 12.15.2 (ArrowFunction, AsyncArrowFunction). + // 14.1.12 (FunctionExpression). + // 14.4.8 (GeneratorExpression). + // 14.6.8 (AsyncFunctionExpression) + if (pn->isKind(PNK_FUNCTION) && !pn->pn_funbox->function()->explicitName()) + return true; + + // 14.5.8 (ClassExpression) + if (pn->is<ClassNode>() && !pn->as<ClassNode>().names()) + return true; + + return false; +} diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index d37aaaae0..ff26279af 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -450,6 +450,9 @@ class ParseNode uint8_t pn_op; /* see JSOp enum and jsopcode.tbl */ uint8_t pn_arity:4; /* see ParseNodeArity enum */ bool pn_parens:1; /* this expr was enclosed in parens */ + bool pn_rhs_anon_fun:1; /* this expr is anonymous function or class that + * is a direct RHS of PNK_ASSIGN or PNK_COLON of + * property, that needs SetFunctionName. */ ParseNode(const ParseNode& other) = delete; void operator=(const ParseNode& other) = delete; @@ -460,6 +463,7 @@ class ParseNode pn_op(op), pn_arity(arity), pn_parens(false), + pn_rhs_anon_fun(false), pn_pos(0, 0), pn_next(nullptr) { @@ -472,6 +476,7 @@ class ParseNode pn_op(op), pn_arity(arity), pn_parens(false), + pn_rhs_anon_fun(false), pn_pos(pos), pn_next(nullptr) { @@ -512,6 +517,13 @@ class ParseNode bool isLikelyIIFE() const { return isInParens(); } void setInParens(bool enabled) { pn_parens = enabled; } + bool isDirectRHSAnonFunction() const { + return pn_rhs_anon_fun; + } + void setDirectRHSAnonFunction(bool enabled) { + pn_rhs_anon_fun = enabled; + } + TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ @@ -1444,6 +1456,9 @@ FunctionFormalParametersList(ParseNode* fn, unsigned* numFormals) return argsBody->pn_head; } +bool +IsAnonymousFunctionDefinition(ParseNode* pn); + } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index f42546eb5..f4c02720a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -326,10 +326,14 @@ ParseContext::init() if (fun->isNamedLambda()) { if (!namedLambdaScope_->init(this)) return false; - AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd(fun->name()); + AddDeclaredNamePtr p = + namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName()); MOZ_ASSERT(!p); - if (!namedLambdaScope_->addDeclaredName(this, p, fun->name(), DeclarationKind::Const)) + if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(), + DeclarationKind::Const)) + { return false; + } } if (!functionScope_->init(this)) @@ -367,7 +371,7 @@ ParseContext::removeInnerFunctionBoxesForAnnexB(JSAtom* name) { for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) { if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i]) { - if (funbox->function()->name() == name) + if (funbox->function()->explicitName() == name) innerFunctionBoxesForAnnexB_[i] = nullptr; } } @@ -465,6 +469,7 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac usesApply(false), usesThis(false), usesReturn(false), + hasRest_(false), funCxFlags() { // Functions created at parse time may be set singleton after parsing and @@ -477,7 +482,6 @@ void FunctionBox::initFromLazyFunction() { JSFunction* fun = function(); - length = fun->nargs() - fun->hasRest(); if (fun->lazyScript()->isDerivedClassConstructor()) setDerivedClassConstructor(); if (fun->lazyScript()->needsHomeObject()) @@ -492,8 +496,6 @@ FunctionBox::initStandaloneFunction(Scope* enclosingScope) // Standalone functions are Function or Generator constructors and are // always scoped to the global. MOZ_ASSERT(enclosingScope->is<GlobalScope>()); - JSFunction* fun = function(); - length = fun->nargs() - fun->hasRest(); enclosingScope_ = enclosingScope; allowNewTarget_ = true; thisBinding_ = ThisBinding::Function; @@ -836,7 +838,7 @@ Parser<ParseHandler>::reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum) { JSAutoByteString name; - if (JSAtom* atom = pc->functionBox()->function()->name()) { + if (JSAtom* atom = pc->functionBox()->function()->explicitName()) { if (!AtomToPrintableString(context, atom, &name)) return false; } else { @@ -2214,6 +2216,8 @@ Parser<SyntaxParseHandler>::finishFunction() lazy->setStrict(); lazy->setGeneratorKind(funbox->generatorKind()); lazy->setAsyncKind(funbox->asyncKind()); + if (funbox->hasRest()) + lazy->setHasRest(); if (funbox->isLikelyConstructorWrapper()) lazy->setLikelyConstructorWrapper(); if (funbox->isDerivedClassConstructor()) @@ -2757,7 +2761,7 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn } hasRest = true; - funbox->function()->setHasRest(); + funbox->setHasRest(); if (!tokenStream.getToken(&tt)) return false; @@ -3477,8 +3481,8 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, if (!body) return false; - if ((kind != Method && !IsConstructorKind(kind)) && fun->name()) { - RootedPropertyName propertyName(context, fun->name()->asPropertyName()); + if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) { + RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName()); if (!checkStrictBinding(propertyName, handler.getPosition(pn))) return false; } @@ -4337,6 +4341,8 @@ Parser<ParseHandler>::declarationPattern(Node decl, DeclarationKind declKind, To if (!init) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(init); + if (forHeadKind) { // For for(;;) declarations, consistency with |for (;| parsing requires // that the ';' first be examined as Operand, even though absence of a @@ -4366,6 +4372,8 @@ Parser<ParseHandler>::initializerInNameDeclaration(Node decl, Node binding, if (!initializer) return false; + handler.checkAndSetIsDirectRHSAnonFunction(initializer); + if (forHeadKind) { if (initialDeclaration) { bool isForIn, isForOf; @@ -5063,7 +5071,7 @@ Parser<FullParseHandler>::exportDeclaration() if (!kid) return null(); - if (!checkExportedName(kid->pn_funbox->function()->name())) + if (!checkExportedName(kid->pn_funbox->function()->explicitName())) return null(); break; @@ -6670,8 +6678,6 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, return null(); } - // FIXME: Implement ES6 function "name" property semantics - // (bug 883377). RootedAtom funName(context); switch (propType) { case PropertyType::GetterNoExpressionClosure: @@ -6694,6 +6700,8 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (!fn) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(fn); + JSOp op = JSOpFromPropertyType(propType); if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic)) return null(); @@ -7753,6 +7761,9 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); } + if (kind == PNK_ASSIGN) + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + return handler.newAssignment(kind, lhs, rhs, op); } @@ -9155,6 +9166,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!propExpr) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(propExpr); + if (foldConstants && !FoldConstants(context, &propExpr, this)) return null(); @@ -9268,6 +9281,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* return null(); } + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); if (!propExpr) return null(); @@ -9278,8 +9293,6 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!abortIfSyntaxParser()) return null(); } else { - // FIXME: Implement ES6 function "name" property semantics - // (bug 883377). RootedAtom funName(context); if (!tokenStream.isCurrentTokenType(TOK_RB)) { funName = propAtom; @@ -9295,6 +9308,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!fn) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(fn); + JSOp op = JSOpFromPropertyType(propType); if (!handler.addObjectMethodDefinition(literal, propName, fn, op)) return null(); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 39df47c20..a6ac542f6 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -471,6 +471,7 @@ class FunctionBox : public ObjectBox, public SharedContext bool usesApply:1; /* contains an f.apply() call */ bool usesThis:1; /* contains 'this' */ bool usesReturn:1; /* contains a 'return' statement */ + bool hasRest_:1; /* has rest parameter */ FunctionContextFlags funCxFlags; @@ -539,6 +540,11 @@ class FunctionBox : public ObjectBox, public SharedContext bool isAsync() const { return asyncKind() == AsyncFunction; } bool isArrow() const { return function()->isArrow(); } + bool hasRest() const { return hasRest_; } + void setHasRest() { + hasRest_ = true; + } + void setGeneratorKind(GeneratorKind kind) { // A generator kind can be set at initialization, or when "yield" is // first seen. In both cases the transition can only happen from @@ -567,7 +573,7 @@ class FunctionBox : public ObjectBox, public SharedContext void setHasInnerFunctions() { funCxFlags.hasInnerFunctions = true; } bool hasSimpleParameterList() const { - return !function()->hasRest() && !hasParameterExprs && !hasDestructuringArgs; + return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; } bool hasMappedArgsObj() const { diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 75c7e3333..b7f00605b 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -339,6 +339,9 @@ class SyntaxParseHandler Node catchGuard, Node catchBody) { return true; } MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(Node funcpn, Node pn) { return true; } + + void checkAndSetIsDirectRHSAnonFunction(Node pn) {} + Node newFunctionDefinition() { return NodeFunctionDefinition; } bool setComprehensionLambdaBody(Node pn, Node body) { return true; } void setFunctionFormalParametersAndBody(Node pn, Node kid) {} diff --git a/js/src/jit-test/tests/basic/constructor-name.js b/js/src/jit-test/tests/basic/constructor-name.js index 4bc6a61ea..2dd4c073f 100644 --- a/js/src/jit-test/tests/basic/constructor-name.js +++ b/js/src/jit-test/tests/basic/constructor-name.js @@ -17,7 +17,7 @@ function makeObject() { let tests = [ { name: "Ctor", object: new Ctor }, { name: "nested.Ctor", object: new nested.Ctor }, - { name: "makeInstance/LexicalCtor", object: makeInstance() }, + { name: "LexicalCtor", object: makeInstance() }, { name: null, object: {} }, { name: null, object: nested.object }, { name: null, object: makeObject() }, diff --git a/js/src/jit-test/tests/basic/functionnames.js b/js/src/jit-test/tests/basic/functionnames.js index 935292ee3..7fef872fc 100644 --- a/js/src/jit-test/tests/basic/functionnames.js +++ b/js/src/jit-test/tests/basic/functionnames.js @@ -40,7 +40,7 @@ assertName(Foo, 'Foo</<'); /* various properties and such */ var x = {fox: { bax: function(){} } }; -assertName(x.fox.bax, 'x.fox.bax'); +assertName(x.fox.bax, 'bax'); var foo = {foo: {foo: {}}}; foo.foo.foo = function(){}; assertName(foo.foo.foo, 'foo.foo.foo'); @@ -48,20 +48,20 @@ var z = { foz: function() { var baz = function() { var y = {bay: function() {}}; - assertName(y.bay, 'z.foz/baz/y.bay'); + assertName(y.bay, 'bay'); }; - assertName(baz, 'z.foz/baz'); + assertName(baz, 'baz'); baz(); } }; -assertName(z.foz, 'z.foz'); +assertName(z.foz, 'foz'); z.foz(); var outer = function() { x.fox.bax.nx = function(){}; var w = {fow: { baw: function(){} } }; assertName(x.fox.bax.nx, 'outer/x.fox.bax.nx') - assertName(w.fow.baw, 'outer/w.fow.baw'); + assertName(w.fow.baw, 'baw'); }; assertName(outer, 'outer'); outer(); @@ -69,7 +69,7 @@ function Fuz(){}; Fuz.prototype = { add: function() {} } -assertName(Fuz.prototype.add, 'Fuz.prototype.add'); +assertName(Fuz.prototype.add, 'add'); var x = 1; x = function(){}; @@ -94,7 +94,7 @@ a.b = function() { assertName(arguments.callee, 'a.b<'); return { a: function() {} } }(); -assertName(a.b.a, 'a.b</<.a'); +assertName(a.b.a, 'a'); a = { b: function(a) { @@ -104,9 +104,9 @@ a = { return function() {}; } }; -assertName(a.b, 'a.b'); -assertName(a.b(true), 'a.b/<') -assertName(a.b(false), 'a.b/<') +assertName(a.b, 'b'); +assertName(a.b(true), 'b/<') +assertName(a.b(false), 'b/<') function f(g) { assertName(g, 'x<'); @@ -116,7 +116,7 @@ var x = f(function () { return function() {}; }); assertName(x, 'x</<'); var a = {'b': function(){}}; -assertName(a.b, 'a.b'); +assertName(a.b, 'b'); function g(f) { assertName(f, ''); @@ -138,15 +138,15 @@ a = { "\"\'quotes\'\"": function(){}, "!@#$%": function(){} }; -assertName(a["embedded spaces"], 'a["embedded spaces"]'); -assertName(a["dots.look.like.property.references"], 'a["dots.look.like.property.references"]'); -assertName(a["\"\'quotes\'\""], 'a["\\\"\'quotes\'\\\""]'); -assertName(a["!@#$%"], 'a["!@#$%"]'); +assertName(a["embedded spaces"], 'embedded spaces'); +assertName(a["dots.look.like.property.references"], 'dots.look.like.property.references'); +assertName(a["\"\'quotes\'\""], '"\'quotes\'"'); +assertName(a["!@#$%"], '!@#$%'); a.b = {}; a.b.c = {}; a.b["c"]["d e"] = { f: { 1: { "g": { "h i": function() {} } } } }; -assertName(a.b.c["d e"].f[1].g["h i"], 'a.b.c["d e"].f[1].g["h i"]'); +assertName(a.b.c["d e"].f[1].g["h i"], 'h i'); this.m = function () {}; assertName(m, "this.m"); diff --git a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js index b147d6ded..5c73a1ad3 100644 --- a/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js +++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js @@ -24,7 +24,7 @@ root.eval( this.tests = [ { name: "Ctor", fn: () => new Ctor }, { name: "nested.Ctor", fn: () => new nested.Ctor }, - { name: "makeInstance/LexicalCtor", fn: () => makeInstance() }, + { name: "LexicalCtor", fn: () => makeInstance() }, { name: null, fn: () => ({}) }, { name: null, fn: () => (nested.object = {}) }, { name: null, fn: () => makeObject() }, diff --git a/js/src/jit-test/tests/saved-stacks/function-display-name.js b/js/src/jit-test/tests/saved-stacks/function-display-name.js index cfe175758..f10b7de6b 100644 --- a/js/src/jit-test/tests/saved-stacks/function-display-name.js +++ b/js/src/jit-test/tests/saved-stacks/function-display-name.js @@ -2,9 +2,8 @@ function uno() { return dos(); } const dos = () => tres.quattro(); -const tres = { - quattro: () => saveStack() -}; +let tres = {}; +tres.quattro = () => saveStack() const frame = uno(); diff --git a/js/src/jit-test/tests/self-test/assertDeepEq.js b/js/src/jit-test/tests/self-test/assertDeepEq.js index b2a949abc..9c1b37e8e 100644 --- a/js/src/jit-test/tests/self-test/assertDeepEq.js +++ b/js/src/jit-test/tests/self-test/assertDeepEq.js @@ -77,7 +77,8 @@ assertDeepEq(q, p); assertNotDeepEq(() => 1, () => 2); assertNotDeepEq((...x) => 1, x => 1); assertNotDeepEq(function f(){}, function g(){}); -var f1 = function () {}, f2 = function () {}; +// Avoid setting name property. +var [f1, f2] = [function () {}, function () {}]; assertDeepEq(f1, f1); assertDeepEq(f1, f2); // same text, close enough f1.prop = 1; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index c58367aa3..4dcc10b61 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -9,6 +9,8 @@ #include "mozilla/Casting.h" #include "mozilla/SizePrintfMacros.h" +#include "jsfun.h" + #include "jit/BaselineIC.h" #include "jit/BaselineJIT.h" #include "jit/FixedList.h" @@ -1681,6 +1683,29 @@ BaselineCompiler::emit_JSOP_LAMBDA_ARROW() return true; } +typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); +static const VMFunction SetFunNameInfo = + FunctionInfo<SetFunNameFn>(js::SetFunctionNameIfNoOwnName, "SetFunName"); + +bool +BaselineCompiler::emit_JSOP_SETFUNNAME() +{ + frame.popRegsAndSync(2); + + frame.push(R0); + frame.syncStack(0); + + FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(pc)); + masm.unboxObject(R0, R0.scratchReg()); + + prepareVMCall(); + + pushArg(Imm32(int32_t(prefixKind))); + pushArg(R1); + pushArg(R0.scratchReg()); + return callVM(SetFunNameInfo); +} + void BaselineCompiler::storeValue(const StackValue* source, const Address& dest, const ValueOperand& scratch) diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 9adf65c27..77f4dd005 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -71,6 +71,7 @@ namespace jit { _(JSOP_REGEXP) \ _(JSOP_LAMBDA) \ _(JSOP_LAMBDA_ARROW) \ + _(JSOP_SETFUNNAME) \ _(JSOP_BITOR) \ _(JSOP_BITXOR) \ _(JSOP_BITAND) \ diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index ce97363be..3b5ec6baa 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2548,6 +2548,20 @@ CodeGenerator::emitLambdaInit(Register output, Register envChain, masm.storePtr(ImmGCPtr(info.fun->displayAtom()), Address(output, JSFunction::offsetOfAtom())); } +typedef bool (*SetFunNameFn)(JSContext*, HandleFunction, HandleValue, FunctionPrefixKind); +static const VMFunction SetFunNameInfo = + FunctionInfo<SetFunNameFn>(js::SetFunctionNameIfNoOwnName, "SetFunName"); + +void +CodeGenerator::visitSetFunName(LSetFunName* lir) +{ + pushArg(Imm32(lir->mir()->prefixKind())); + pushArg(ToValue(lir, LSetFunName::NameValue)); + pushArg(ToRegister(lir->fun())); + + callVM(SetFunNameInfo, lir); +} + void CodeGenerator::visitOsiPoint(LOsiPoint* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 8f4bcc813..b69e919a3 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -134,6 +134,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitOutOfLineLambdaArrow(OutOfLineLambdaArrow* ool); void visitLambdaArrow(LLambdaArrow* lir); void visitLambdaForSingleton(LLambdaForSingleton* lir); + void visitSetFunName(LSetFunName* lir); void visitPointer(LPointer* lir); void visitKeepAliveObject(LKeepAliveObject* lir); void visitSlots(LSlots* lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 1488d7d34..2e7784ff4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2122,6 +2122,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_LAMBDA_ARROW: return jsop_lambda_arrow(info().getFunction(pc)); + case JSOP_SETFUNNAME: + return jsop_setfunname(GET_UINT8(pc)); + case JSOP_ITER: return jsop_iter(GET_INT8(pc)); @@ -13340,6 +13343,21 @@ IonBuilder::jsop_lambda_arrow(JSFunction* fun) } bool +IonBuilder::jsop_setfunname(uint8_t prefixKind) +{ + MDefinition* name = current->pop(); + MDefinition* fun = current->pop(); + MOZ_ASSERT(fun->type() == MIRType::Object); + + MSetFunName* ins = MSetFunName::New(alloc(), fun, name, prefixKind); + + current->add(ins); + current->push(fun); + + return resumeAfter(ins); +} + +bool IonBuilder::jsop_setarg(uint32_t arg) { // To handle this case, we should spill the arguments to the space where diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 38647a88f..0d1bdb1e3 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -766,6 +766,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_object(JSObject* obj); MOZ_MUST_USE bool jsop_lambda(JSFunction* fun); MOZ_MUST_USE bool jsop_lambda_arrow(JSFunction* fun); + MOZ_MUST_USE bool jsop_setfunname(uint8_t prefixKind); MOZ_MUST_USE bool jsop_functionthis(); MOZ_MUST_USE bool jsop_globalthis(); MOZ_MUST_USE bool jsop_typeof(); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 13e50820e..a21a529be 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2460,6 +2460,18 @@ LIRGenerator::visitLambdaArrow(MLambdaArrow* ins) } void +LIRGenerator::visitSetFunName(MSetFunName* ins) +{ + MOZ_ASSERT(ins->fun()->type() == MIRType::Object); + MOZ_ASSERT(ins->name()->type() == MIRType::Value); + + LSetFunName* lir = new(alloc()) LSetFunName(useRegisterAtStart(ins->fun()), + useBoxAtStart(ins->name())); + add(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitKeepAliveObject(MKeepAliveObject* ins) { MDefinition* obj = ins->object(); diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 0f66a3c24..4062c0960 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -185,6 +185,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitNullarySharedStub(MNullarySharedStub* ins); void visitLambda(MLambda* ins); void visitLambdaArrow(MLambdaArrow* ins); + void visitSetFunName(MSetFunName* ins); void visitKeepAliveObject(MKeepAliveObject* ins); void visitSlots(MSlots* ins); void visitElements(MElements* ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index dcb08c317..3caa7e357 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -8464,6 +8464,34 @@ class MLambdaArrow } }; +class MSetFunName + : public MAryInstruction<2>, + public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >::Data +{ + uint8_t prefixKind_; + + explicit MSetFunName(MDefinition* fun, MDefinition* name, uint8_t prefixKind) + : prefixKind_(prefixKind) + { + initOperand(0, fun); + initOperand(1, name); + setResultType(MIRType::None); + } + + public: + INSTRUCTION_HEADER(SetFunName) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, fun), (1, name)) + + uint8_t prefixKind() const { + return prefixKind_; + } + + bool possiblyCalls() const override { + return true; + } +}; + // Returns obj->slots. class MSlots : public MUnaryInstruction, diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 74594cb35..1a6911247 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -161,6 +161,7 @@ namespace jit { _(StringReplace) \ _(Lambda) \ _(LambdaArrow) \ + _(SetFunName) \ _(KeepAliveObject) \ _(Slots) \ _(Elements) \ diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index a352f5d8a..f8e0ce9cc 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -4995,6 +4995,25 @@ class LLambdaArrow : public LInstructionHelper<1, 1 + BOX_PIECES, 0> } }; +class LSetFunName : public LCallInstructionHelper<1, 1 + BOX_PIECES, 0> +{ + public: + LIR_HEADER(SetFunName) + + static const size_t NameValue = 1; + + LSetFunName(const LAllocation& fun, const LBoxAllocation& name) { + setOperand(0, fun); + setBoxOperand(NameValue, name); + } + const LAllocation* fun() { + return getOperand(0); + } + const MSetFunName* mir() const { + return mir_->toSetFunName(); + } +}; + class LKeepAliveObject : public LInstructionHelper<0, 1, 0> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index bb04553a6..e57751437 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -242,6 +242,7 @@ _(Lambda) \ _(LambdaArrow) \ _(LambdaForSingleton) \ + _(SetFunName) \ _(KeepAliveObject) \ _(Slots) \ _(Elements) \ diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index e6fc1f98b..85a38bba4 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2095,7 +2095,7 @@ DefinePropertyById(JSContext* cx, HandleObject obj, HandleId id, HandleValue val getter != JS_PropertyStub && setter != JS_StrictPropertyStub) { if (getter && !(attrs & JSPROP_GETTER)) { - RootedAtom atom(cx, IdToFunctionName(cx, id, "get")); + RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get)); if (!atom) return false; JSFunction* getobj = NewNativeFunction(cx, (Native) getter, 0, atom); @@ -2111,7 +2111,7 @@ DefinePropertyById(JSContext* cx, HandleObject obj, HandleId id, HandleValue val if (setter && !(attrs & JSPROP_SETTER)) { // Root just the getter, since the setter is not yet a JSObject. AutoRooterGetterSetter getRoot(cx, JSPROP_GETTER, &getter, nullptr); - RootedAtom atom(cx, IdToFunctionName(cx, id, "set")); + RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Set)); if (!atom) return false; JSFunction* setobj = NewNativeFunction(cx, (Native) setter, 1, atom); @@ -3435,10 +3435,7 @@ JS::NewFunctionFromSpec(JSContext* cx, const JSFunctionSpec* fs, HandleId id) { return nullptr; } - JSFunction* fun = &funVal.toObject().as<JSFunction>(); - if (fs->flags & JSFUN_HAS_REST) - fun->setHasRest(); - return fun; + return &funVal.toObject().as<JSFunction>(); } RootedAtom atom(cx, IdToFunctionName(cx, id)); @@ -3618,7 +3615,7 @@ JS_GetFunctionObject(JSFunction* fun) JS_PUBLIC_API(JSString*) JS_GetFunctionId(JSFunction* fun) { - return fun->name(); + return fun->explicitName(); } JS_PUBLIC_API(JSString*) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 332ce8562..cbef0f8fb 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -875,11 +875,7 @@ class MOZ_STACK_CLASS SourceBufferHolder final #define JSFUN_CONSTRUCTOR 0x400 /* native that can be called as a ctor */ -// 0x800 /* Unused */ - -#define JSFUN_HAS_REST 0x1000 /* function has ...rest parameter. */ - -#define JSFUN_FLAGS_MASK 0x1e00 /* | of all the JSFUN_* flags */ +#define JSFUN_FLAGS_MASK 0x600 /* | of all the JSFUN_* flags */ /* * If set, will allow redefining a non-configurable property, but only on a diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 3f8e8d8f8..2a3c58638 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -510,6 +510,14 @@ ToAtomSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::HandleTyp return v.toBoolean() ? cx->names().true_ : cx->names().false_; if (v.isNull()) return cx->names().null; + if (v.isSymbol()) { + if (cx->shouldBeJSContext() && allowGC) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_SYMBOL_TO_STRING); + } + return nullptr; + } + MOZ_ASSERT(v.isUndefined()); return cx->names().undefined; } diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 3ffd9ad7b..31d62332d 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -803,7 +803,7 @@ js::ReportMissingArg(JSContext* cx, HandleValue v, unsigned arg) SprintfLiteral(argbuf, "%u", arg); if (IsFunctionObject(v)) { - RootedAtom name(cx, v.toObject().as<JSFunction>().name()); + RootedAtom name(cx, v.toObject().as<JSFunction>().explicitName()); bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, name); if (!bytes) return; diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 1e1b76d5d..bcb0da80b 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -562,7 +562,7 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope, return false; } - if (fun->name() || fun->hasGuessedAtom()) + if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom()) firstword |= HasAtom; if (fun->isStarGenerator()) @@ -1042,8 +1042,8 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) if (!ok) return nullptr; } - if (fun->name()) { - if (!out.append(fun->name())) + if (fun->explicitName()) { + if (!out.append(fun->explicitName())) return nullptr; } @@ -1329,8 +1329,7 @@ JSFunction::getLength(JSContext* cx, uint16_t* length) if (self->isInterpretedLazy() && !self->getOrCreateScript(cx)) return false; - *length = self->hasScript() ? self->nonLazyScript()->funLength() - : (self->nargs() - self->hasRest()); + *length = self->isNative() ? self->nargs() : self->nonLazyScript()->funLength(); return true; } @@ -1365,14 +1364,14 @@ JSFunction::getUnresolvedName(JSContext* cx) if (isClassConstructor()) { // It's impossible to have an empty named class expression. We use // empty as a sentinel when creating default class constructors. - MOZ_ASSERT(name() != cx->names().empty); + MOZ_ASSERT(explicitOrCompileTimeName() != cx->names().empty); // Unnamed class expressions should not get a .name property at all. - return name(); + return explicitOrCompileTimeName(); } - // Returns the empty string for unnamed functions (FIXME: bug 883377). - return name() != nullptr ? name() : cx->names().empty; + return explicitOrCompileTimeName() != nullptr ? explicitOrCompileTimeName() + : cx->names().empty; } static const js::Value& @@ -1620,7 +1619,7 @@ const JSFunctionSpec js::function_methods[] = { JS_FN(js_apply_str, fun_apply, 2,0), JS_FN(js_call_str, fun_call, 1,0), JS_FN("isGenerator", fun_isGenerator,0,0), - JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, JSFUN_HAS_REST), + JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, 0), JS_SYM_FN(hasInstance, fun_symbolHasInstance, 1, JSPROP_READONLY | JSPROP_PERMANENT), JS_FS_END }; @@ -2132,34 +2131,115 @@ js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun, HandleObject enclo * * Function names are always strings. If id is the well-known @@iterator * symbol, this returns "[Symbol.iterator]". If a prefix is supplied the final - * name is |prefix + " " + name|. + * name is |prefix + " " + name|. A prefix cannot be supplied if id is a + * symbol value. * - * Implements step 4 and 5 of SetFunctionName in ES 2016 draft Dec 20, 2015. + * Implements steps 3-5 of 9.2.11 SetFunctionName in ES2016. */ JSAtom* -js::IdToFunctionName(JSContext* cx, HandleId id, const char* prefix /* = nullptr */) +js::IdToFunctionName(JSContext* cx, HandleId id, + FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */) { - if (JSID_IS_ATOM(id) && !prefix) + // No prefix fastpath. + if (JSID_IS_ATOM(id) && prefixKind == FunctionPrefixKind::None) return JSID_TO_ATOM(id); - if (JSID_IS_SYMBOL(id) && !prefix) { + // Step 3 (implicit). + + // Step 4. + if (JSID_IS_SYMBOL(id)) { + // Step 4.a. RootedAtom desc(cx, JSID_TO_SYMBOL(id)->description()); + + // Step 4.b, no prefix fastpath. + if (!desc && prefixKind == FunctionPrefixKind::None) + return cx->names().empty; + + // Step 5 (reordered). StringBuffer sb(cx); - if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) - return nullptr; + if (prefixKind == FunctionPrefixKind::Get) { + if (!sb.append("get ")) + return nullptr; + } else if (prefixKind == FunctionPrefixKind::Set) { + if (!sb.append("set ")) + return nullptr; + } + + // Step 4.b. + if (desc) { + // Step 4.c. + if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) + return nullptr; + } return sb.finishAtom(); } RootedValue idv(cx, IdToValue(id)); - if (!prefix) - return ToAtom<CanGC>(cx, idv); + RootedAtom name(cx, ToAtom<CanGC>(cx, idv)); + if (!name) + return nullptr; + + // Step 5. + return NameToFunctionName(cx, name, prefixKind); +} + +JSAtom* +js::NameToFunctionName(ExclusiveContext* cx, HandleAtom name, + FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */) +{ + if (prefixKind == FunctionPrefixKind::None) + return name; StringBuffer sb(cx); - if (!sb.append(prefix, strlen(prefix)) || !sb.append(' ') || !sb.append(ToAtom<CanGC>(cx, idv))) + if (prefixKind == FunctionPrefixKind::Get) { + if (!sb.append("get ")) + return nullptr; + } else { + if (!sb.append("set ")) + return nullptr; + } + if (!sb.append(name)) return nullptr; return sb.finishAtom(); } +bool +js::SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name, + FunctionPrefixKind prefixKind) +{ + MOZ_ASSERT(name.isString() || name.isSymbol() || name.isNumber()); + + if (fun->isClassConstructor()) { + // A class may have static 'name' method or accessor. + RootedId nameId(cx, NameToId(cx->names().name)); + bool result; + if (!HasOwnProperty(cx, fun, nameId, &result)) + return false; + + if (result) + return true; + } else { + // Anonymous function shouldn't have own 'name' property at this point. + MOZ_ASSERT(!fun->containsPure(cx->names().name)); + } + + RootedId id(cx); + if (!ValueToId<CanGC>(cx, name, &id)) + return false; + + RootedAtom funNameAtom(cx, IdToFunctionName(cx, id, prefixKind)); + if (!funNameAtom) + return false; + + RootedValue funNameVal(cx, StringValue(funNameAtom)); + if (!NativeDefineProperty(cx, fun, cx->names().name, funNameVal, nullptr, nullptr, + JSPROP_READONLY)) + { + return false; + } + return true; +} + JSFunction* js::DefineFunction(JSContext* cx, HandleObject obj, HandleId id, Native native, unsigned nargs, unsigned flags, AllocKind allocKind /* = AllocKind::FUNCTION */) diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 88af5c22d..d45d112a5 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -31,6 +31,12 @@ static const uint32_t JSSLOT_BOUND_FUNCTION_ARGS = 4; static const char FunctionConstructorMedialSigils[] = ") {\n"; static const char FunctionConstructorFinalBrace[] = "\n}"; +enum class FunctionPrefixKind { + None, + Get, + Set +}; + class JSFunction : public js::NativeObject { public: @@ -61,7 +67,9 @@ class JSFunction : public js::NativeObject function-statement) */ SELF_HOSTED = 0x0080, /* function is self-hosted builtin and must not be decompilable nor constructible. */ - HAS_REST = 0x0100, /* function has a rest (...) parameter */ + HAS_COMPILE_TIME_NAME = 0x0100, /* function had no explicit name, but a + name was set by SetFunctionName + at compile time */ INTERPRETED_LAZY = 0x0200, /* function is interpreted but doesn't have a script yet */ RESOLVED_LENGTH = 0x0400, /* f.length has been resolved (see fun_resolve). */ RESOLVED_NAME = 0x0800, /* f.name has been resolved (see fun_resolve). */ @@ -95,7 +103,7 @@ class JSFunction : public js::NativeObject NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME, STABLE_ACROSS_CLONES = CONSTRUCTOR | EXPR_BODY | HAS_GUESSED_ATOM | LAMBDA | - SELF_HOSTED | HAS_REST | FUNCTION_KIND_MASK + SELF_HOSTED | HAS_COMPILE_TIME_NAME | FUNCTION_KIND_MASK }; static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, @@ -180,10 +188,10 @@ class JSFunction : public js::NativeObject /* Possible attributes of an interpreted function: */ bool isExprBody() const { return flags() & EXPR_BODY; } + 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 hasRest() const { return flags() & HAS_REST; } bool isInterpretedLazy() const { return flags() & INTERPRETED_LAZY; } bool hasScript() const { return flags() & INTERPRETED; } @@ -222,7 +230,7 @@ class JSFunction : public js::NativeObject } bool isNamedLambda() const { - return isLambda() && displayAtom() && !hasGuessedAtom(); + return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom(); } bool hasLexicalThis() const { @@ -264,11 +272,6 @@ class JSFunction : public js::NativeObject this->nargs_ = nargs; } - // Can be called multiple times by the parser. - void setHasRest() { - flags_ |= HAS_REST; - } - void setIsBoundFunction() { MOZ_ASSERT(!isBoundFunction()); flags_ |= BOUND_FUN; @@ -315,14 +318,12 @@ class JSFunction : public js::NativeObject JSAtom* getUnresolvedName(JSContext* cx); - JSAtom* name() const { return hasGuessedAtom() ? nullptr : atom_.get(); } - - // Because display names (see Debugger.Object.displayName) are already stored - // on functions and will always contain a valid es6 function name, as described - // in "ECMA-262 (2016-02-27) 9.2.11 SetFunctionName," we have opted to save - // memory by parsing the existing display name when a function's name property - // is accessed. - JSAtom* functionName(JSContext* cx) const; + JSAtom* explicitName() const { + return (hasCompileTimeName() || hasGuessedAtom()) ? nullptr : atom_.get(); + } + JSAtom* explicitOrCompileTimeName() const { + return hasGuessedAtom() ? nullptr : atom_.get(); + } void initAtom(JSAtom* atom) { atom_.init(atom); } @@ -332,9 +333,24 @@ class JSFunction : public js::NativeObject return atom_; } + void setCompileTimeName(JSAtom* atom) { + MOZ_ASSERT(!atom_); + MOZ_ASSERT(atom); + MOZ_ASSERT(!hasGuessedAtom()); + MOZ_ASSERT(!isClassConstructor()); + atom_ = atom; + flags_ |= HAS_COMPILE_TIME_NAME; + } + JSAtom* compileTimeName() const { + MOZ_ASSERT(hasCompileTimeName()); + MOZ_ASSERT(atom_); + return atom_; + } + void setGuessedAtom(JSAtom* atom) { MOZ_ASSERT(!atom_); MOZ_ASSERT(atom); + MOZ_ASSERT(!hasCompileTimeName()); MOZ_ASSERT(!hasGuessedAtom()); atom_ = atom; flags_ |= HAS_GUESSED_ATOM; @@ -679,7 +695,16 @@ NewFunctionWithProto(ExclusiveContext* cx, JSNative native, unsigned nargs, NewFunctionProtoHandling protoHandling = NewFunctionClassProto); extern JSAtom* -IdToFunctionName(JSContext* cx, HandleId id, const char* prefix = nullptr); +IdToFunctionName(JSContext* cx, HandleId id, + FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + +extern JSAtom* +NameToFunctionName(ExclusiveContext* cx, HandleAtom name, + FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + +extern bool +SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name, + FunctionPrefixKind prefixKind); extern JSFunction* DefineFunction(JSContext* cx, HandleObject obj, HandleId id, JSNative native, diff --git a/js/src/jsfuninlines.h b/js/src/jsfuninlines.h index 8286a7d6a..e134def61 100644 --- a/js/src/jsfuninlines.h +++ b/js/src/jsfuninlines.h @@ -16,7 +16,7 @@ namespace js { inline const char* GetFunctionNameBytes(JSContext* cx, JSFunction* fun, JSAutoByteString* bytes) { - if (JSAtom* name = fun->name()) + if (JSAtom* name = fun->explicitName()) return bytes->encodeLatin1(cx, name); return js_anonymous_str; } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index b568b4b30..929251d8b 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -316,6 +316,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip IsLegacyGenerator, IsStarGenerator, IsAsync, + HasRest, OwnSource, ExplicitUseStrict, SelfHosted, @@ -431,6 +432,8 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip scriptBits |= (1 << IsStarGenerator); if (script->asyncKind() == AsyncFunction) scriptBits |= (1 << IsAsync); + if (script->hasRest()) + scriptBits |= (1 << HasRest); if (script->hasSingletons()) scriptBits |= (1 << HasSingleton); if (script->treatAsRunOnce()) @@ -582,6 +585,8 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip if (scriptBits & (1 << IsAsync)) script->setAsyncKind(AsyncFunction); + if (scriptBits & (1 << HasRest)) + script->setHasRest(); } JS_STATIC_ASSERT(sizeof(jsbytecode) == 1); @@ -2637,6 +2642,8 @@ JSScript::initFromFunctionBox(ExclusiveContext* cx, HandleScript script, script->isGeneratorExp_ = funbox->isGenexpLambda; script->setGeneratorKind(funbox->generatorKind()); script->setAsyncKind(funbox->asyncKind()); + if (funbox->hasRest()) + script->setHasRest(); PositionalFormalParameterIter fi(script); while (fi && !fi.closedOver()) @@ -3295,6 +3302,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, dst->needsHomeObject_ = src->needsHomeObject(); dst->isDefaultClassConstructor_ = src->isDefaultClassConstructor(); dst->isAsync_ = src->asyncKind() == AsyncFunction; + dst->hasRest_ = src->hasRest_; if (nconsts != 0) { GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector); @@ -4028,6 +4036,7 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, p.shouldDeclareArguments = false; p.hasThisBinding = false; p.isAsync = false; + p.hasRest = false; p.numClosedOverBindings = closedOverBindings.length(); p.numInnerFunctions = innerFunctions.length(); p.generatorKindBits = GeneratorKindAsBits(NotGenerator); @@ -4169,7 +4178,7 @@ JSScript::hasLoops() bool JSScript::mayReadFrameArgsDirectly() { - return argumentsHasVarBinding() || (function() && function()->hasRest()); + return argumentsHasVarBinding() || hasRest(); } static inline void diff --git a/js/src/jsscript.h b/js/src/jsscript.h index ffd4b1e2e..690bc225d 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1012,6 +1012,8 @@ class JSScript : public js::gc::TenuredCell bool isAsync_:1; + bool hasRest_:1; + // Add padding so JSScript is gc::Cell aligned. Make padding protected // instead of private to suppress -Wunused-private-field compiler warnings. protected: @@ -1308,6 +1310,13 @@ class JSScript : public js::gc::TenuredCell isAsync_ = kind == js::AsyncFunction; } + bool hasRest() const { + return hasRest_; + } + void setHasRest() { + hasRest_ = true; + } + void setNeedsHomeObject() { needsHomeObject_ = true; } @@ -1940,6 +1949,7 @@ class LazyScript : public gc::TenuredCell uint32_t treatAsRunOnce : 1; uint32_t isDerivedClassConstructor : 1; uint32_t needsHomeObject : 1; + uint32_t hasRest : 1; }; union { @@ -2068,6 +2078,13 @@ class LazyScript : public gc::TenuredCell p_.isAsync = kind == AsyncFunction; } + bool hasRest() const { + return p_.hasRest; + } + void setHasRest() { + p_.hasRest = true; + } + bool strict() const { return p_.strict; } diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 32a85dc13..7adeed620 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -2894,7 +2894,7 @@ static const JSFunctionSpec string_static_methods[] = { JS_INLINABLE_FN("fromCharCode", js::str_fromCharCode, 1, 0, StringFromCharCode), JS_INLINABLE_FN("fromCodePoint", js::str_fromCodePoint, 1, 0, StringFromCodePoint), - JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,JSFUN_HAS_REST), + JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,0), JS_SELF_HOSTED_FN("substring", "String_static_substring", 3,0), JS_SELF_HOSTED_FN("substr", "String_static_substr", 3,0), JS_SELF_HOSTED_FN("slice", "String_static_slice", 3,0), diff --git a/js/src/tests/ecma_5/extensions/error-tostring-function.js b/js/src/tests/ecma_5/extensions/error-tostring-function.js index 5e92f1075..86751c39d 100644 --- a/js/src/tests/ecma_5/extensions/error-tostring-function.js +++ b/js/src/tests/ecma_5/extensions/error-tostring-function.js @@ -27,7 +27,7 @@ assertEq(ErrorToString(function(){}), ""); var fn1 = function() {}; fn1.message = "ohai"; -assertEq(ErrorToString(fn1), "ohai"); +assertEq(ErrorToString(fn1), "fn1: ohai"); var fn2 = function blerch() {}; fn2.message = "fnord"; @@ -35,7 +35,7 @@ assertEq(ErrorToString(fn2), "blerch: fnord"); var fn3 = function() {}; fn3.message = ""; -assertEq(ErrorToString(fn3), ""); +assertEq(ErrorToString(fn3), "fn3"); /******************************************************************************/ diff --git a/js/src/tests/ecma_6/Class/className.js b/js/src/tests/ecma_6/Class/className.js index a33397a8a..ad3920c15 100644 --- a/js/src/tests/ecma_6/Class/className.js +++ b/js/src/tests/ecma_6/Class/className.js @@ -174,27 +174,29 @@ testName(ExtendedExpr3, "base", false, false, false); // Anonymous class expressions don't get name properties unless specified in a // static manner. -let Anon = class { +// Use property assignment to avoid setting name property. +let tmp = {}; +let Anon = tmp.value = class { constructor() {} }; testName(Anon, "", false, false, false); -let AnonDefault = class { }; +let AnonDefault = tmp.value = class { }; testName(AnonDefault, "", false, false, false); -let AnonWithGetter = class { +let AnonWithGetter = tmp.value = class { constructor() {} static get name() { return "base"; } }; testName(AnonWithGetter, "base", false, true, false); -let AnonWithSetter = class { +let AnonWithSetter = tmp.value = class { constructor() {} static set name(v) {} }; testName(AnonWithSetter, undefined, false, false, true); -let AnonWithGetterSetter = class { +let AnonWithGetterSetter = tmp.value = class { constructor() {} static get name() { return "base"; } static set name(v) {} @@ -202,15 +204,15 @@ let AnonWithGetterSetter = class { testName(AnonWithGetterSetter, "base", false, true, true); -let ExtendedAnon1 = class extends Anon { +let ExtendedAnon1 = tmp.value = class extends Anon { constructor() {} }; testName(ExtendedAnon1, "", false, false, false); -let ExtendedAnonDefault = class extends Anon { }; +let ExtendedAnonDefault = tmp.value = class extends Anon { }; testName(ExtendedAnonDefault, "", false, false, false); -let ExtendedAnon2 = class extends AnonWithGetterSetter { +let ExtendedAnon2 = tmp.value = class extends AnonWithGetterSetter { constructor() {} static get name() { return "extend"; } }; @@ -218,7 +220,7 @@ testName(ExtendedAnon2, "extend", false, true, false); delete ExtendedAnon2.name; testName(ExtendedAnon2, "base", false, false, false); -let ExtendedAnon3 = class extends AnonWithGetterSetter { +let ExtendedAnon3 = tmp.value = class extends AnonWithGetterSetter { constructor() {} static set name(v) {} }; diff --git a/js/src/tests/ecma_6/Function/function-name-assignment.js b/js/src/tests/ecma_6/Function/function-name-assignment.js new file mode 100644 index 000000000..5e4d1c004 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-assignment.js @@ -0,0 +1,139 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on assignment"; + +print(BUGNUMBER + ": " + summary); + +var fooSymbol = Symbol("foo"); +var emptySymbol = Symbol(""); +var undefSymbol = Symbol(); +var globalVar; + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testAssignmentExpression(expr, named) { + eval(` + var assignment; + assignment = ${expr}; + assertEq(assignment.name, named ? "named" : "assignment"); + + globalVar = ${expr}; + assertEq(globalVar.name, named ? "named" : "globalVar"); + + var obj = { dynamic: null }; + with (obj) { + dynamic = ${expr}; + } + assertEq(obj.dynamic.name, named ? "named" : "dynamic"); + + (function namedLambda(param1, param2) { + var assignedToNamedLambda; + assignedToNamedLambda = namedLambda = ${expr}; + assertEq(namedLambda.name, "namedLambda"); + assertEq(assignedToNamedLambda.name, named ? "named" : "namedLambda"); + + param1 = ${expr}; + assertEq(param1.name, named ? "named" : "param1"); + + { + let param1 = ${expr}; + assertEq(param1.name, named ? "named" : "param1"); + + param2 = ${expr}; + assertEq(param2.name, named ? "named" : "param2"); + } + })(); + + { + let nextedLexical1, nextedLexical2; + { + let nextedLexical1 = ${expr}; + assertEq(nextedLexical1.name, named ? "named" : "nextedLexical1"); + + nextedLexical2 = ${expr}; + assertEq(nextedLexical2.name, named ? "named" : "nextedLexical2"); + } + } + `); + + // Not applicable cases: not IsIdentifierRef. + eval(` + var inParen; + (inParen) = ${expr}; + assertEq(inParen.name, named ? "named" : ""); + `); + + // Not applicable cases: not direct RHS. + if (!expr.includes("=>")) { + eval(` + var a = true && ${expr}; + assertEq(a.name, named ? "named" : ""); + `); + } else { + // Arrow function cannot be RHS of &&. + eval(` + var a = true && (${expr}); + assertEq(a.name, named ? "named" : ""); + `); + } + + // Not applicable cases: property. + eval(` + var obj = {}; + + obj.prop = ${expr}; + assertEq(obj.prop.name, named ? "named" : ""); + + obj["literal"] = ${expr}; + assertEq(obj["literal"].name, named ? "named" : ""); + `); + + // Not applicable cases: assigned again. + eval(` + var tmp = [${expr}]; + assertEq(tmp[0].name, named ? "named" : ""); + + var assignment; + assignment = tmp[0]; + assertEq(assignment.name, named ? "named" : ""); + `); +} +for (var [expr, named] of exprs) { + testAssignmentExpression(expr, named); +} + +function testVariableDeclaration(expr, named) { + eval(` + var varDecl = ${expr}; + assertEq(varDecl.name, named ? "named" : "varDecl"); + `); +} +for (var [expr, named] of exprs) { + testVariableDeclaration(expr, named); +} + +function testLexicalBinding(expr, named) { + eval(` + let lexical = ${expr}; + assertEq(lexical.name, named ? "named" : "lexical"); + + const constLexical = ${expr}; + assertEq(constLexical.name, named ? "named" : "constLexical"); + `); +} +for (var [expr, named] of exprs) { + testLexicalBinding(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-binding.js b/js/src/tests/ecma_6/Function/function-name-binding.js new file mode 100644 index 000000000..bdd6c131c --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-binding.js @@ -0,0 +1,54 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on binding pattern"; + +print(BUGNUMBER + ": " + summary); + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testAssignmentProperty(expr, named) { + var f = eval(`(function({ prop1 = ${expr} }) { return prop1; })`); + assertEq(f({}).name, named ? "named" : "prop1"); + + eval(` + var { prop1 = ${expr} } = {}; + assertEq(prop1.name, named ? "named" : "prop1"); + `); +} +for (var [expr, named] of exprs) { + testAssignmentProperty(expr, named); +} + +function testAssignmentElement(expr, named) { + var f = eval(`(function([elem1 = ${expr}]) { return elem1; })`); + assertEq(f([]).name, named ? "named" : "elem1"); + + eval(` + var [elem1 = ${expr}] = []; + assertEq(elem1.name, named ? "named" : "elem1"); + `); +} +for (var [expr, named] of exprs) { + testAssignmentElement(expr, named); +} + +function testSingleNameBinding(expr, named) { + var f = eval(`(function(param1 = ${expr}) { return param1; })`); + assertEq(f().name, named ? "named" : "param1"); +} +for (var [expr, named] of exprs) { + testSingleNameBinding(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-class.js b/js/src/tests/ecma_6/Function/function-name-class.js new file mode 100644 index 000000000..edde69055 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-class.js @@ -0,0 +1,32 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous class with name method shouldn't be affected by assignment"; + +print(BUGNUMBER + ": " + summary); + +var classWithStaticNameMethod = class { static name() {} }; +assertEq(typeof classWithStaticNameMethod.name, "function"); + +var classWithStaticNameGetter = class { static get name() { return "static name"; } }; +assertEq(typeof Object.getOwnPropertyDescriptor(classWithStaticNameGetter, "name").get, "function"); +assertEq(classWithStaticNameGetter.name, "static name"); + +var classWithStaticNameSetter = class { static set name(v) {} }; +assertEq(typeof Object.getOwnPropertyDescriptor(classWithStaticNameSetter, "name").set, "function"); + +var n = "NAME".toLowerCase(); +var classWithStaticNameMethodComputed = class { static [n]() {} }; +assertEq(typeof classWithStaticNameMethodComputed.name, "function"); + +// It doesn't apply for non-static method. + +var classWithNameMethod = class { name() {} }; +assertEq(classWithNameMethod.name, "classWithNameMethod"); + +var classWithNameGetter = class { get name() { return "name"; } }; +assertEq(classWithNameGetter.name, "classWithNameGetter"); + +var classWithNameSetter = class { set name(v) {} }; +assertEq(classWithNameSetter.name, "classWithNameSetter"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-for.js b/js/src/tests/ecma_6/Function/function-name-for.js new file mode 100644 index 000000000..2f04a5fa8 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-for.js @@ -0,0 +1,31 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on for-in initializer"; + +print(BUGNUMBER + ": " + summary); + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testForInHead(expr, named) { + eval(` + for (var forInHead = ${expr} in {}) { + } + `); + assertEq(forInHead.name, named ? "named" : "forInHead"); +} +for (var [expr, named] of exprs) { + testForInHead(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-method.js b/js/src/tests/ecma_6/Function/function-name-method.js new file mode 100644 index 000000000..3b2eeee79 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-method.js @@ -0,0 +1,70 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on method definition"; + +print(BUGNUMBER + ": " + summary); + +var fooSymbol = Symbol("foo"); +var emptySymbol = Symbol(""); +var undefSymbol = Symbol(); + +function testMethod(prefix, classPrefix="", prototype=false) { + var param = (prefix == "set" || prefix == "static set") ? "v" : ""; + var sep = classPrefix ? "" : ","; + var objOrClass = eval(`(${classPrefix}{ + ${prefix} prop(${param}) {} ${sep} + ${prefix} "literal"(${param}) {} ${sep} + ${prefix} ""(${param}) {} ${sep} + ${prefix} 5(${param}) {} ${sep} + ${prefix} [Symbol.iterator](${param}) {} ${sep} + ${prefix} [fooSymbol](${param}) {} ${sep} + ${prefix} [emptySymbol](${param}) {} ${sep} + ${prefix} [undefSymbol](${param}) {} ${sep} + ${prefix} [/a/](${param}) {} ${sep} + })`); + + var target = prototype ? objOrClass.prototype : objOrClass; + + function testOne(methodName, expectedName) { + var f; + if (prefix == "get" || prefix == "static get") { + f = Object.getOwnPropertyDescriptor(target, methodName).get; + expectedName = "get " + expectedName; + } else if (prefix == "set" || prefix == "static set") { + f = Object.getOwnPropertyDescriptor(target, methodName).set; + expectedName = "set " + expectedName; + } else { + f = Object.getOwnPropertyDescriptor(target, methodName).value; + } + + assertEq(f.name, expectedName); + } + testOne("prop", "prop"); + testOne("literal", "literal"); + testOne("", ""); + testOne(5, "5"); + testOne(Symbol.iterator, "[Symbol.iterator]"); + testOne(fooSymbol, "[foo]"); + testOne(emptySymbol, "[]"); + testOne(undefSymbol, ""); + testOne(/a/, "/a/"); +} +testMethod(""); +testMethod("*"); +testMethod("async"); +testMethod("get"); +testMethod("set"); + +testMethod("", "class", true); +testMethod("*", "class", true); +testMethod("async", "class", true); +testMethod("get", "class", true); +testMethod("set", "class", true); + +testMethod("static", "class"); +testMethod("static *", "class"); +testMethod("static async", "class"); +testMethod("static get", "class"); +testMethod("static set", "class"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/function-name-property.js b/js/src/tests/ecma_6/Function/function-name-property.js new file mode 100644 index 000000000..7ad174b10 --- /dev/null +++ b/js/src/tests/ecma_6/Function/function-name-property.js @@ -0,0 +1,58 @@ +var BUGNUMBER = 883377; +var summary = "Anonymous function name should be set based on property name"; + +print(BUGNUMBER + ": " + summary); + +var fooSymbol = Symbol("foo"); +var emptySymbol = Symbol(""); +var undefSymbol = Symbol(); + +var exprs = [ + ["function() {}", false], + ["function named() {}", true], + ["function*() {}", false], + ["function* named() {}", true], + ["async function() {}", false], + ["async function named() {}", true], + ["() => {}", false], + ["async () => {}", false], + ["class {}", false], + ["class named {}", true], +]; + +function testPropertyDefinition(expr, named) { + var obj = eval(`({ + prop: ${expr}, + "literal": ${expr}, + "": ${expr}, + 5: ${expr}, + 0.4: ${expr}, + [Symbol.iterator]: ${expr}, + [fooSymbol]: ${expr}, + [emptySymbol]: ${expr}, + [undefSymbol]: ${expr}, + [/a/]: ${expr}, + })`); + assertEq(obj.prop.name, named ? "named" : "prop"); + assertEq(obj["literal"].name, named ? "named" : "literal"); + assertEq(obj[""].name, named ? "named" : ""); + assertEq(obj[5].name, named ? "named" : "5"); + assertEq(obj[0.4].name, named ? "named" : "0.4"); + assertEq(obj[Symbol.iterator].name, named ? "named" : "[Symbol.iterator]"); + assertEq(obj[fooSymbol].name, named ? "named" : "[foo]"); + assertEq(obj[emptySymbol].name, named ? "named" : "[]"); + assertEq(obj[undefSymbol].name, named ? "named" : ""); + assertEq(obj[/a/].name, named ? "named" : "/a/"); + + // Not applicable cases: __proto__. + obj = { + __proto__: function() {} + }; + assertEq(obj.__proto__.name, ""); +} +for (var [expr, named] of exprs) { + testPropertyDefinition(expr, named); +} + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Object/accessor-name.js b/js/src/tests/ecma_6/Object/accessor-name.js index 1b5268e07..f238a2aef 100644 --- a/js/src/tests/ecma_6/Object/accessor-name.js +++ b/js/src/tests/ecma_6/Object/accessor-name.js @@ -27,10 +27,9 @@ o = {get case() { }, set case(v) {}} assertEq(name(o, "case", true), "get case"); assertEq(name(o, "case", false), "set case"); -// Congratulations on implementing these! -assertEq(name({get ["a"]() {}}, "a", true), ""); -assertEq(name({get [123]() {}}, "123", true), ""); -assertEq(name({set ["a"](v) {}}, "a", false), ""); -assertEq(name({set [123](v) {}}, "123", false), ""); +assertEq(name({get ["a"]() {}}, "a", true), "get a"); +assertEq(name({get [123]() {}}, "123", true), "get 123"); +assertEq(name({set ["a"](v) {}}, "a", false), "set a"); +assertEq(name({set [123](v) {}}, "123", false), "set 123"); reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/compile-symbol.js b/js/src/tests/ecma_6/RegExp/compile-symbol.js new file mode 100644 index 000000000..9eea1124c --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/compile-symbol.js @@ -0,0 +1,14 @@ +/* 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/. */ + +for (let sym of [Symbol.iterator, Symbol(), Symbol("description")]) { + let re = /a/; + + assertEq(re.source, "a"); + assertThrowsInstanceOf(() => re.compile(sym), TypeError); + assertEq(re.source, "a"); +} + +if (typeof reportCompare === 'function') + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/RegExp/constructor-symbol.js b/js/src/tests/ecma_6/RegExp/constructor-symbol.js new file mode 100644 index 000000000..503d7e5a8 --- /dev/null +++ b/js/src/tests/ecma_6/RegExp/constructor-symbol.js @@ -0,0 +1,14 @@ +/* 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/. */ + +for (let sym of [Symbol.iterator, Symbol(), Symbol("description")]) { + assertThrowsInstanceOf(() => RegExp(sym), TypeError); + assertThrowsInstanceOf(() => new RegExp(sym), TypeError); + + assertThrowsInstanceOf(() => RegExp(sym, "g"), TypeError); + assertThrowsInstanceOf(() => new RegExp(sym, "g"), TypeError); +} + +if (typeof reportCompare === 'function') + reportCompare(0, 0); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index 1e0c7d7c2..f50c87114 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -116,7 +116,7 @@ js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleOb // Create a new function with AsyncFunctionPrototype, reusing the name and // the length of `unwrapped`. - RootedAtom funName(cx, unwrapped->name()); + RootedAtom funName(cx, unwrapped->explicitName()); uint16_t length; if (!unwrapped->getLength(cx, &length)) return nullptr; @@ -130,6 +130,9 @@ js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleOb if (!wrapped) return nullptr; + if (unwrapped->hasCompileTimeName()) + wrapped->setCompileTimeName(unwrapped->compileTimeName()); + // Link them to each other to make GetWrappedAsyncFunction and // GetUnwrappedAsyncFunction work. unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped)); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 4d181545f..d16781326 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -9509,7 +9509,7 @@ DebuggerObject::name() const { MOZ_ASSERT(isFunction()); - return referent()->as<JSFunction>().name(); + return referent()->as<JSFunction>().explicitName(); } JSAtom* diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 039be2e32..280548cd6 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -802,10 +802,10 @@ GlobalObject::getSelfHostedFunction(JSContext* cx, Handle<GlobalObject*> global, return false; if (exists) { RootedFunction fun(cx, &funVal.toObject().as<JSFunction>()); - if (fun->name() == name) + if (fun->explicitName() == name) return true; - if (fun->name() == selfHostedName) { + if (fun->explicitName() == selfHostedName) { // This function was initially cloned because it was called by // other self-hosted code, so the clone kept its self-hosted name, // instead of getting the name it's intended to have in content diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index fbf526ae5..9cba1f4dc 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -282,8 +282,6 @@ MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto) ctor->setIsConstructor(); ctor->setIsClassConstructor(); - if (derived) - ctor->setHasRest(); MOZ_ASSERT(ctor->infallibleIsDefaultClassConstructor(cx)); @@ -1862,7 +1860,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED182) CASE(JSOP_UNUSED183) CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED192) @@ -3484,6 +3481,19 @@ CASE(JSOP_TOASYNC) } END_CASE(JSOP_TOASYNC) +CASE(JSOP_SETFUNNAME) +{ + MOZ_ASSERT(REGS.stackDepth() >= 2); + FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc)); + ReservedRooted<Value> name(&rootValue0, REGS.sp[-1]); + ReservedRooted<JSFunction*> fun(&rootFunction0, ®S.sp[-2].toObject().as<JSFunction>()); + if (!SetFunctionNameIfNoOwnName(cx, fun, name, prefixKind)) + goto error; + + REGS.sp--; +} +END_CASE(JSOP_SETFUNNAME) + CASE(JSOP_CALLEE) MOZ_ASSERT(REGS.fp()->isFunctionFrame()); PUSH_COPY(REGS.fp()->calleev()); @@ -4345,7 +4355,7 @@ js::DefFunOperation(JSContext* cx, HandleScript script, HandleObject envChain, parent = parent->enclosingEnvironment(); /* ES5 10.5 (NB: with subsequent errata). */ - RootedPropertyName name(cx, fun->name()->asPropertyName()); + RootedPropertyName name(cx, fun->explicitName()->asPropertyName()); RootedShape shape(cx); RootedObject pobj(cx); @@ -4993,7 +5003,7 @@ js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber, RootedPropertyName name(cx); if (op == JSOP_THROWSETCALLEE) { - name = script->functionNonDelazifying()->name()->asPropertyName(); + name = script->functionNonDelazifying()->explicitName()->asPropertyName(); } else if (IsLocalOp(op)) { name = FrameSlotName(script, pc)->asPropertyName(); } else if (IsAtomOp(op)) { @@ -5061,8 +5071,8 @@ js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) if (fun->isDerivedClassConstructor()) { const char* name = "anonymous"; JSAutoByteString str; - if (fun->name()) { - if (!AtomToPrintableString(cx, fun->name(), &str)) + if (fun->explicitName()) { + if (!AtomToPrintableString(cx, fun->explicitName(), &str)) return false; name = str.ptr(); } diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 18ae6f073..f6636004d 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1870,7 +1870,16 @@ * Stack: => */ \ macro(JSOP_POPVARENV, 181, "popvarenv", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED182, 182,"unused182", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the top two values on the stack as 'name' and 'fun', defines the + * name of 'fun' to 'name' with prefix if any, and pushes 'fun' back onto + * the stack. + * Category: Statements + * Type: Function + * Operands: uint8_t prefixKind + * Stack: fun, name => fun + */ \ + macro(JSOP_SETFUNNAME, 182,"setfunname", NULL, 2, 2, 1, JOF_UINT8) \ macro(JSOP_UNUSED183, 183,"unused183", NULL, 1, 0, 0, JOF_BYTE) \ \ /* diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 6737e774c..bf49f2268 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2878,7 +2878,7 @@ CloneObject(JSContext* cx, HandleNativeObject selfHostedObject) RootedObject clone(cx); if (selfHostedObject->is<JSFunction>()) { RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>()); - bool hasName = selfHostedFunction->name() != nullptr; + bool hasName = selfHostedFunction->explicitName() != nullptr; // Arrow functions use the first extended slot for their lexical |this| value. MOZ_ASSERT(!selfHostedFunction->isArrow()); @@ -2894,7 +2894,7 @@ CloneObject(JSContext* cx, HandleNativeObject selfHostedObject) // self-hosting compartment has to be stored on the clone. if (clone && hasName) { clone->as<JSFunction>().setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, - StringValue(selfHostedFunction->name())); + StringValue(selfHostedFunction->explicitName())); } } else if (selfHostedObject->is<RegExpObject>()) { RegExpObject& reobj = selfHostedObject->as<RegExpObject>(); @@ -2977,10 +2977,10 @@ JSRuntime::createLazySelfHostedFunctionClone(JSContext* cx, HandlePropertyName s return false; if (!selfHostedFun->isClassConstructor() && !selfHostedFun->hasGuessedAtom() && - selfHostedFun->name() != selfHostedName) + selfHostedFun->explicitName() != selfHostedName) { MOZ_ASSERT(selfHostedFun->getExtendedSlot(HAS_SELFHOSTED_CANONICAL_NAME_SLOT).toBoolean()); - funName = selfHostedFun->name(); + funName = selfHostedFun->explicitName(); } fun.set(NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY, @@ -3022,7 +3022,7 @@ JSRuntime::cloneSelfHostedFunctionScript(JSContext* cx, HandlePropertyName name, MOZ_ASSERT(!targetFun->isInterpretedLazy()); MOZ_ASSERT(sourceFun->nargs() == targetFun->nargs()); - MOZ_ASSERT(sourceFun->hasRest() == targetFun->hasRest()); + MOZ_ASSERT(sourceScript->hasRest() == targetFun->nonLazyScript()->hasRest()); // The target function might have been relazified after its flags changed. targetFun->setFlags(targetFun->flags() | sourceFun->flags()); diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 439bb1ed4..87e95c893 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -85,7 +85,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const JSObject* InterpreterFrame::createRestParameter(JSContext* cx) { - MOZ_ASSERT(callee().hasRest()); + MOZ_ASSERT(script()->hasRest()); unsigned nformal = callee().nargs() - 1, nactual = numActualArgs(); unsigned nrest = (nactual > nformal) ? nactual - nformal : 0; Value* restvp = argv() + nformal; diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 2a7762e4f..3d09c7464 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -4541,7 +4541,7 @@ TypeScript::printTypes(JSContext* cx, HandleScript script) const uintptr_t(script.get()), script->filename(), script->lineno()); if (script->functionNonDelazifying()) { - if (JSAtom* name = script->functionNonDelazifying()->name()) + if (JSAtom* name = script->functionNonDelazifying()->explicitName()) name->dumpCharsNoNewline(); } diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 9d4ee94c6..ae97be0de 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -2870,7 +2870,7 @@ bool DataViewObject::defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto) { RootedId id(cx, NameToId(name)); - RootedAtom atom(cx, IdToFunctionName(cx, id, "get")); + RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get)); if (!atom) return false; unsigned attrs = JSPROP_SHARED | JSPROP_GETTER; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 4dbc9b387..b4f41c3d5 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -679,7 +679,7 @@ FunctionObject(ParseNode* fn) static inline PropertyName* FunctionName(ParseNode* fn) { - if (JSAtom* name = FunctionObject(fn)->name()) + if (JSAtom* name = FunctionObject(fn)->explicitName()) return name->asPropertyName(); return nullptr; } @@ -3249,7 +3249,7 @@ static bool CheckFunctionHead(ModuleValidator& m, ParseNode* fn) { JSFunction* fun = FunctionObject(fn); - if (fun->hasRest()) + if (fn->pn_funbox->hasRest()) return m.fail(fn, "rest args not allowed"); if (fun->isExprBody()) return m.fail(fn, "expression closures not allowed"); @@ -8037,7 +8037,7 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata static bool HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata) { - RootedAtom name(cx, args.callee().as<JSFunction>().name()); + RootedAtom name(cx, args.callee().as<JSFunction>().explicitName()); if (cx->isExceptionPending()) return false; @@ -8128,7 +8128,7 @@ InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) static JSFunction* NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj) { - RootedAtom name(cx, origFun->name()); + RootedAtom name(cx, origFun->explicitName()); JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR : JSFunction::ASMJS_CTOR; @@ -8846,7 +8846,7 @@ js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda if (!out.append("function ")) return nullptr; - if (fun->name() && !out.append(fun->name())) + if (fun->explicitName() && !out.append(fun->explicitName())) return nullptr; bool haveSource = source->hasSourceData(); @@ -8894,8 +8894,8 @@ js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) if (!haveSource) { // asm.js functions can't be anonymous - MOZ_ASSERT(fun->name()); - if (!out.append(fun->name())) + MOZ_ASSERT(fun->explicitName()); + if (!out.append(fun->explicitName())) return nullptr; if (!out.append("() {\n [sourceless code]\n}")) return nullptr; |