diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-03-18 17:54:38 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-03-18 17:56:16 +0100 |
commit | 122938a4398ae8db07060dca3561ff46f93b5925 (patch) | |
tree | 965b4bcf2ea8133c172170cf66e5c87cf1e17df3 /js/src/jsfun.cpp | |
parent | dda392cd4edb3258889188af5a5644eb8d36aeb7 (diff) | |
parent | af300f36f11293c12f2ee01580fc749a7e114376 (diff) | |
download | UXP-122938a4398ae8db07060dca3561ff46f93b5925.tar UXP-122938a4398ae8db07060dca3561ff46f93b5925.tar.gz UXP-122938a4398ae8db07060dca3561ff46f93b5925.tar.lz UXP-122938a4398ae8db07060dca3561ff46f93b5925.tar.xz UXP-122938a4398ae8db07060dca3561ff46f93b5925.zip |
Support ES6's "new function" construct
This resolves #75.
Merged remote-tracking branch 'janek/js_function_new_1'
Diffstat (limited to 'js/src/jsfun.cpp')
-rw-r--r-- | js/src/jsfun.cpp | 308 |
1 files changed, 97 insertions, 211 deletions
diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index c952441ad..1e1b76d5d 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) @@ -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; } @@ -1045,7 +1047,6 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) 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()); @@ -1657,17 +1612,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), @@ -1681,13 +1625,12 @@ const JSFunctionSpec js::function_methods[] = { 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 +1662,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(); + // Remember the position of ")". + Maybe<uint32_t> parameterListEnd = Some(uint32_t(sb.length())); + MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')'); - // 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; - } - - 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 +1729,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 +1758,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 +1770,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 +1784,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; |