diff options
Diffstat (limited to 'js/src/jsfun.cpp')
-rw-r--r-- | js/src/jsfun.cpp | 428 |
1 files changed, 197 insertions, 231 deletions
diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index c952441ad5..bcb0da80b2 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -12,6 +12,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/CheckedInt.h" +#include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Range.h" @@ -60,8 +61,10 @@ using namespace js::gc; using namespace js::frontend; using mozilla::ArrayLength; +using mozilla::Maybe; using mozilla::PodCopy; using mozilla::RangedPtr; +using mozilla::Some; static bool fun_enumerate(JSContext* cx, HandleObject obj) @@ -559,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()) @@ -985,19 +988,19 @@ js::FindBody(JSContext* cx, HandleFunction fun, HandleLinearString src, size_t* } JSString* -js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) +js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) { if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx)) return nullptr; if (IsAsmJSModule(fun)) - return AsmJSModuleToString(cx, fun, !lambdaParen); + return AsmJSModuleToString(cx, fun, !prettyPrint); if (IsAsmJSFunction(fun)) return AsmJSFunctionToString(cx, fun); if (IsWrappedAsyncFunction(fun)) { RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun)); - return FunctionToString(cx, unwrapped, lambdaParen); + return FunctionToString(cx, unwrapped, prettyPrint); } StringBuffer out(cx); @@ -1023,11 +1026,10 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) bool funIsMethodOrNonArrowLambda = (fun->isLambda() && !fun->isArrow()) || fun->isMethod() || fun->isGetter() || fun->isSetter(); + bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); // If we're not in pretty mode, put parentheses around lambda functions and methods. - if (fun->isInterpreted() && !lambdaParen && funIsMethodOrNonArrowLambda && - !fun->isSelfHostedBuiltin()) - { + if (haveSource && !prettyPrint && funIsMethodOrNonArrowLambda) { if (!out.append("(")) return nullptr; } @@ -1040,12 +1042,11 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) if (!ok) return nullptr; } - if (fun->name()) { - if (!out.append(fun->name())) + if (fun->explicitName()) { + if (!out.append(fun->explicitName())) return nullptr; } - bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); if (haveSource && !script->scriptSource()->hasSourceData() && !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) { @@ -1056,54 +1057,10 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) if (!src) return nullptr; - // The source data for functions created by calling the Function - // constructor is only the function's body. This depends on the fact, - // asserted below, that in Function("function f() {}"), the inner - // function's sourceStart points to the '(', not the 'f'. - bool funCon = !fun->isArrow() && - script->sourceStart() == 0 && - script->sourceEnd() == script->scriptSource()->length() && - script->scriptSource()->argumentsNotIncluded(); - - // Functions created with the constructor can't be arrow functions or - // expression closures. - MOZ_ASSERT_IF(funCon, !fun->isArrow()); - MOZ_ASSERT_IF(funCon, !fun->isExprBody()); - MOZ_ASSERT_IF(!funCon && !fun->isArrow(), - src->length() > 0 && src->latin1OrTwoByteChar(0) == '('); - - bool buildBody = funCon; - if (buildBody) { - // This function was created with the Function constructor. We don't - // have source for the arguments, so we have to generate that. Part - // of bug 755821 should be cobbling the arguments passed into the - // Function constructor into the source string. - if (!out.append("(")) - return nullptr; - - // Fish out the argument names. - MOZ_ASSERT(script->numArgs() == fun->nargs()); - - BindingIter bi(script); - for (unsigned i = 0; i < fun->nargs(); i++, bi++) { - MOZ_ASSERT(bi.argumentSlot() == i); - if (i && !out.append(", ")) - return nullptr; - if (i == unsigned(fun->nargs() - 1) && fun->hasRest() && !out.append("...")) - return nullptr; - if (!out.append(bi.name())) - return nullptr; - } - if (!out.append(") {\n")) - return nullptr; - } if (!out.append(src)) return nullptr; - if (buildBody) { - if (!out.append("\n}")) - return nullptr; - } - if (!lambdaParen && funIsMethodOrNonArrowLambda) { + + if (!prettyPrint && funIsMethodOrNonArrowLambda) { if (!out.append(")")) return nullptr; } @@ -1114,8 +1071,6 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) { return nullptr; } - if (!lambdaParen && fun->isLambda() && !fun->isArrow() && !out.append(")")) - return nullptr; } else { MOZ_ASSERT(!fun->isExprBody()); @@ -1374,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; } @@ -1410,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& @@ -1657,17 +1611,6 @@ fun_isGenerator(JSContext* cx, unsigned argc, Value* vp) return true; } -/* - * Report "malformed formal parameter" iff no illegal char or similar scanner - * error was already reported. - */ -static bool -OnBadFormal(JSContext* cx) -{ - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_FORMAL); - return false; -} - const JSFunctionSpec js::function_methods[] = { #if JS_HAS_TOSOURCE JS_FN(js_toSource_str, fun_toSource, 0,0), @@ -1676,18 +1619,17 @@ 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 }; +// ES 2017 draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 19.2.1.1.1. static bool -FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind generatorKind, +FunctionConstructor(JSContext* cx, const CallArgs& args, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { - CallArgs args = CallArgsFromVp(argc, vp); - - /* Block this call if security callbacks forbid it. */ + // Block this call if security callbacks forbid it. Rooted<GlobalObject*> global(cx, &args.callee().global()); if (!GlobalObject::isRuntimeCodeGenEnabled(cx, global)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_FUNCTION); @@ -1719,82 +1661,65 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener introducerFilename = maybeScript->scriptSource()->introducerFilename(); CompileOptions options(cx); + // Use line 0 to make the function body starts from line 1. options.setMutedErrors(mutedErrors) - .setFileAndLine(filename, 1) + .setFileAndLine(filename, 0) .setNoScriptRval(false) .setIntroductionInfo(introducerFilename, introductionType, lineno, maybeScript, pcOffset); - Vector<char16_t> paramStr(cx); - RootedString bodyText(cx); + StringBuffer sb(cx); - if (args.length() == 0) { - bodyText = cx->names().empty; - } else { - // Collect the function-argument arguments into one string, separated - // by commas, then make a tokenstream from that string, and scan it to - // get the arguments. We need to throw the full scanner at the - // problem because the argument string may contain comments, newlines, - // destructuring arguments, and similar manner of insanities. ("I have - // a feeling we're not in simple-comma-separated-parameters land any - // more, Toto....") - // - // XXX It'd be better if the parser provided utility methods to parse - // an argument list, and to parse a function body given a parameter - // list. But our parser provides no such pleasant interface now. - unsigned n = args.length() - 1; + if (!sb.append('(')) + return false; - // Convert the parameters-related arguments to strings, and determine - // the length of the string containing the overall parameter list. - mozilla::CheckedInt<uint32_t> paramStrLen = 0; + if (args.length() > 1) { RootedString str(cx); + + // Steps 5-6, 9. + unsigned n = args.length() - 1; + for (unsigned i = 0; i < n; i++) { + // Steps 9.a-b, 9.d.i-ii. str = ToString<CanGC>(cx, args[i]); if (!str) return false; - args[i].setString(str); - paramStrLen += str->length(); - } - - // Tack in space for any combining commas. - if (n > 0) - paramStrLen += n - 1; + // Steps 9.b, 9.d.iii. + if (!sb.append(str)) + return false; - // Check for integer and string-size overflow. - if (!paramStrLen.isValid() || paramStrLen.value() > JSString::MAX_LENGTH) { - ReportAllocationOverflow(cx); - return false; + if (i < args.length() - 2) { + // Step 9.d.iii. + if (!sb.append(", ")) + return false; + } } + } - uint32_t paramsLen = paramStrLen.value(); - - // Fill a vector with the comma-joined arguments. Careful! This - // string is *not* null-terminated! - MOZ_ASSERT(paramStr.length() == 0); - if (!paramStr.growBy(paramsLen)) { - ReportOutOfMemory(cx); - return false; - } + // Remember the position of ")". + Maybe<uint32_t> parameterListEnd = Some(uint32_t(sb.length())); + MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')'); - char16_t* cp = paramStr.begin(); - for (unsigned i = 0; i < n; i++) { - JSLinearString* argLinear = args[i].toString()->ensureLinear(cx); - if (!argLinear) - return false; + if (!sb.append(FunctionConstructorMedialSigils)) + return false; - CopyChars(cp, *argLinear); - cp += argLinear->length(); + if (args.length() > 0) { + // Steps 7-8, 10. + RootedString body(cx, ToString<CanGC>(cx, args[args.length() - 1])); + if (!body || !sb.append(body)) + return false; + } - if (i + 1 < n) - *cp++ = ','; - } + if (!sb.append(FunctionConstructorFinalBrace)) + return false; - MOZ_ASSERT(cp == paramStr.end()); + // The parser only accepts two byte strings. + if (!sb.ensureTwoByteChars()) + return false; - bodyText = ToString(cx, args[n]); - if (!bodyText) - return false; - } + RootedString functionText(cx, sb.finishString()); + if (!functionText) + return false; /* * NB: (new Function) is not lexically closed by its caller, it's just an @@ -1803,18 +1728,23 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener * and so would a call to f from another top-level's script or function. */ RootedAtom anonymousAtom(cx, cx->names().anonymous); + + // Step 24. RootedObject proto(cx); - if (isStarGenerator) { - // Unwrapped function of async function should use GeneratorFunction, - // while wrapped function isn't generator. + if (!isAsync) { + if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) + return false; + } + + // Step 4.d, use %Generator% as the fallback prototype. + // Also use %Generator% for the unwrapped function of async functions. + if (!proto && isStarGenerator) { proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global); if (!proto) return false; - } else { - if (!GetPrototypeFromCallableConstructor(cx, args, &proto)) - return false; } + // Step 25-32 (reordered). RootedObject globalLexical(cx, &global->lexicalEnvironment()); AllocKind allocKind = isAsync ? AllocKind::FUNCTION_EXTENDED : AllocKind::FUNCTION; RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0, @@ -1827,81 +1757,11 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener if (!JSFunction::setTypeForScriptedFunction(cx, fun)) return false; + // Steps 2.a-b, 3.a-b, 4.a-b, 11-23. AutoStableStringChars stableChars(cx); - if (!stableChars.initTwoByte(cx, bodyText)) + if (!stableChars.initTwoByte(cx, functionText)) return false; - bool hasRest = false; - - Rooted<PropertyNameVector> formals(cx, PropertyNameVector(cx)); - if (args.length() > 1) { - // Initialize a tokenstream to parse the new function's arguments. No - // StrictModeGetter is needed because this TokenStream won't report any - // strict mode errors. Strict mode errors that might be reported here - // (duplicate argument names, etc.) will be detected when we compile - // the function body. - // - // XXX Bug! We have to parse the body first to determine strictness. - // We have to know strictness to parse arguments correctly, in case - // arguments contains a strict mode violation. And we should be - // using full-fledged arguments parsing here, in order to handle - // destructuring and other exotic syntaxes. - AutoKeepAtoms keepAtoms(cx->perThreadData); - TokenStream ts(cx, options, paramStr.begin(), paramStr.length(), - /* strictModeGetter = */ nullptr); - bool yieldIsValidName = ts.versionNumber() < JSVERSION_1_7 && !isStarGenerator; - - // The argument string may be empty or contain no tokens. - TokenKind tt; - if (!ts.getToken(&tt)) - return false; - if (tt != TOK_EOF) { - while (true) { - // Check that it's a name. - if (hasRest) { - ts.reportError(JSMSG_PARAMETER_AFTER_REST); - return false; - } - - if (tt == TOK_YIELD && yieldIsValidName) - tt = TOK_NAME; - - if (tt != TOK_NAME) { - if (tt == TOK_TRIPLEDOT) { - hasRest = true; - if (!ts.getToken(&tt)) - return false; - if (tt == TOK_YIELD && yieldIsValidName) - tt = TOK_NAME; - if (tt != TOK_NAME) { - ts.reportError(JSMSG_NO_REST_NAME); - return false; - } - } else { - return OnBadFormal(cx); - } - } - - if (!formals.append(ts.currentName())) - return false; - - // Get the next token. Stop on end of stream. Otherwise - // insist on a comma, get another name, and iterate. - if (!ts.getToken(&tt)) - return false; - if (tt == TOK_EOF) - break; - if (tt != TOK_COMMA) - return OnBadFormal(cx); - if (!ts.getToken(&tt)) - return false; - } - } - } - - if (hasRest) - fun->setHasRest(); - mozilla::Range<const char16_t> chars = stableChars.twoByteRange(); SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership @@ -1909,11 +1769,13 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener bool ok; SourceBufferHolder srcBuf(chars.begin().get(), chars.length(), ownership); if (isAsync) - ok = frontend::CompileAsyncFunctionBody(cx, &fun, options, formals, srcBuf); + ok = frontend::CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf, parameterListEnd); else if (isStarGenerator) - ok = frontend::CompileStarGeneratorBody(cx, &fun, options, formals, srcBuf); + ok = frontend::CompileStandaloneGenerator(cx, &fun, options, srcBuf, parameterListEnd); else - ok = frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf); + ok = frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, parameterListEnd); + + // Step 33. args.rval().setObject(*fun); return ok; } @@ -1921,24 +1783,47 @@ FunctionConstructor(JSContext* cx, unsigned argc, Value* vp, GeneratorKind gener bool js::Function(JSContext* cx, unsigned argc, Value* vp) { - return FunctionConstructor(cx, argc, vp, NotGenerator, SyncFunction); + CallArgs args = CallArgsFromVp(argc, vp); + return FunctionConstructor(cx, args, NotGenerator, SyncFunction); } bool js::Generator(JSContext* cx, unsigned argc, Value* vp) { - return FunctionConstructor(cx, argc, vp, StarGenerator, SyncFunction); + CallArgs args = CallArgsFromVp(argc, vp); + return FunctionConstructor(cx, args, StarGenerator, SyncFunction); } bool js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - if (!FunctionConstructor(cx, argc, vp, StarGenerator, AsyncFunction)) + + // Save the callee before its reset in FunctionConstructor(). + RootedObject newTarget(cx); + if (args.isConstructing()) + newTarget = &args.newTarget().toObject(); + else + newTarget = &args.callee(); + + if (!FunctionConstructor(cx, args, StarGenerator, AsyncFunction)) + return false; + + // ES2017, draft rev 0f10dba4ad18de92d47d421f378233a2eae8f077 + // 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction, step 24. + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; + // 19.2.1.1.1, step 4.d, use %AsyncFunctionPrototype% as the fallback. + if (!proto) { + proto = GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()); + if (!proto) + return false; + } + RootedFunction unwrapped(cx, &args.rval().toObject().as<JSFunction>()); - RootedObject wrapped(cx, WrapAsyncFunction(cx, unwrapped)); + RootedObject wrapped(cx, WrapAsyncFunctionWithProto(cx, unwrapped, proto)); if (!wrapped) return false; @@ -2246,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 */) |