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 | |
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')
29 files changed, 900 insertions, 502 deletions
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 990a4acdb..3a20c487b 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -765,42 +765,53 @@ static const JSFunctionSpec collator_methods[] = { }; /** - * Collator constructor. - * Spec: ECMAScript Internationalization API Specification, 10.1 + * 10.1.2 Intl.Collator([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool Collator(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when Collator is called because of + // backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 10.1.2.1 step 3 + // ES Intl 1st ed., 10.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 10.1.2.1 step 4 + // ES Intl 1st ed., 10.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 10.1.2.1 step 5 + // ES Intl 1st ed., 10.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 10.1.2.1 step 3.a + // ES Intl 1st ed., 10.1.2.1 step 3.a construct = true; } } if (construct) { - // 10.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateCollatorPrototype(cx)); - if (!proto) + // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateCollatorPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &CollatorClass, proto); if (!obj) return false; @@ -808,15 +819,13 @@ Collator(JSContext* cx, const CallArgs& args, bool construct) obj->as<NativeObject>().setReservedSlot(UCOLLATOR_SLOT, PrivateValue(nullptr)); } - // 10.1.2.1 steps 1 and 2; 10.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 10.1.2.1 step 6; 10.1.3.1 step 3 + // Step 6. if (!IntlInitialize(cx, obj, cx->names().InitializeCollator, locales, options)) return false; - // 10.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -833,6 +842,7 @@ js::intl_Collator(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_Collator is an intrinsic for self-hosted JavaScript, so it cannot // be used with "new", but it still has to be treated as a constructor. return Collator(cx, args, true); @@ -1257,42 +1267,53 @@ static const JSFunctionSpec numberFormat_methods[] = { }; /** - * NumberFormat constructor. - * Spec: ECMAScript Internationalization API Specification, 11.1 + * 11.2.1 Intl.NumberFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when NumberFormat is called + // because of backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 11.1.2.1 step 3 + // ES Intl 1st ed., 11.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 11.1.2.1 step 4 + // ES Intl 1st ed., 11.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 11.1.2.1 step 5 + // ES Intl 1st ed., 11.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 11.1.2.1 step 3.a + // ES Intl 1st ed., 11.1.2.1 step 3.a construct = true; } } if (construct) { - // 11.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateNumberFormatPrototype(cx)); - if (!proto) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateNumberFormatPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &NumberFormatClass, proto); if (!obj) return false; @@ -1300,15 +1321,13 @@ NumberFormat(JSContext* cx, const CallArgs& args, bool construct) obj->as<NativeObject>().setReservedSlot(UNUMBER_FORMAT_SLOT, PrivateValue(nullptr)); } - // 11.1.2.1 steps 1 and 2; 11.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 11.1.2.1 step 6; 11.1.3.1 step 3 + // Step 3. if (!IntlInitialize(cx, obj, cx->names().InitializeNumberFormat, locales, options)) return false; - // 11.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -1325,6 +1344,7 @@ js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it // cannot be used with "new", but it still has to be treated as a // constructor. @@ -1725,42 +1745,53 @@ static const JSFunctionSpec dateTimeFormat_methods[] = { }; /** - * DateTimeFormat constructor. - * Spec: ECMAScript Internationalization API Specification, 12.1 + * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) + * + * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) { RootedObject obj(cx); + // We're following ECMA-402 1st Edition when DateTimeFormat is called + // because of backward compatibility issues. + // See https://github.com/tc39/ecma402/issues/57 if (!construct) { - // 12.1.2.1 step 3 + // ES Intl 1st ed., 12.1.2.1 step 3 JSObject* intl = cx->global()->getOrCreateIntlObject(cx); if (!intl) return false; RootedValue self(cx, args.thisv()); if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) { - // 12.1.2.1 step 4 + // ES Intl 1st ed., 12.1.2.1 step 4 obj = ToObject(cx, self); if (!obj) return false; - // 12.1.2.1 step 5 + // ES Intl 1st ed., 12.1.2.1 step 5 bool extensible; if (!IsExtensible(cx, obj, &extensible)) return false; if (!extensible) return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE); } else { - // 12.1.2.1 step 3.a + // ES Intl 1st ed., 12.1.2.1 step 3.a construct = true; } } if (construct) { - // 12.1.3.1 paragraph 2 - RootedObject proto(cx, cx->global()->getOrCreateDateTimeFormatPrototype(cx)); - if (!proto) + // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor). + RootedObject proto(cx); + if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto)) return false; + + if (!proto) { + proto = cx->global()->getOrCreateDateTimeFormatPrototype(cx); + if (!proto) + return false; + } + obj = NewObjectWithGivenProto(cx, &DateTimeFormatClass, proto); if (!obj) return false; @@ -1768,15 +1799,13 @@ DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct) obj->as<NativeObject>().setReservedSlot(UDATE_FORMAT_SLOT, PrivateValue(nullptr)); } - // 12.1.2.1 steps 1 and 2; 12.1.3.1 steps 1 and 2 RootedValue locales(cx, args.length() > 0 ? args[0] : UndefinedValue()); RootedValue options(cx, args.length() > 1 ? args[1] : UndefinedValue()); - // 12.1.2.1 step 6; 12.1.3.1 step 3 + // Step 3. if (!IntlInitialize(cx, obj, cx->names().InitializeDateTimeFormat, locales, options)) return false; - // 12.1.2.1 steps 3.a and 7 args.rval().setObject(*obj); return true; } @@ -1793,6 +1822,7 @@ js::intl_DateTimeFormat(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(!args.isConstructing()); // intl_DateTimeFormat is an intrinsic for self-hosted JavaScript, so it // cannot be used with "new", but it still has to be treated as a // constructor. diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index d4c758b6c..3fbfdaa1a 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -7,6 +7,7 @@ #include "frontend/BytecodeCompiler.h" #include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Maybe.h" #include "jscntxt.h" #include "jsscript.h" @@ -28,6 +29,7 @@ using namespace js; using namespace js::frontend; using mozilla::Maybe; +using mozilla::Nothing; class MOZ_STACK_CLASS AutoCompilationTraceLogger { @@ -57,24 +59,24 @@ class MOZ_STACK_CLASS BytecodeCompiler // Call setters for optional arguments. void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor); - void setSourceArgumentsNotIncluded(); JSScript* compileGlobalScript(ScopeKind scopeKind); JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope); ModuleObject* compileModule(); - bool compileFunctionBody(MutableHandleFunction fun, Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind); + bool compileStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Maybe<uint32_t> parameterListEnd); ScriptSourceObject* sourceObjectPtr() const; private: JSScript* compileScript(HandleObject environment, SharedContext* sc); bool checkLength(); - bool createScriptSource(); + bool createScriptSource(Maybe<uint32_t> parameterListEnd); bool maybeCompressSource(); bool canLazilyParse(); bool createParser(); - bool createSourceAndParser(); + bool createSourceAndParser(Maybe<uint32_t> parameterListEnd = Nothing()); bool createScript(); bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext); bool handleParseFailure(const Directives& newDirectives); @@ -90,7 +92,6 @@ class MOZ_STACK_CLASS BytecodeCompiler SourceBufferHolder& sourceBuffer; RootedScope enclosingScope; - bool sourceArgumentsNotIncluded; RootedScriptSource sourceObject; ScriptSource* scriptSource; @@ -130,7 +131,6 @@ BytecodeCompiler::BytecodeCompiler(ExclusiveContext* cx, options(options), sourceBuffer(sourceBuffer), enclosingScope(cx, enclosingScope), - sourceArgumentsNotIncluded(false), sourceObject(cx), scriptSource(nullptr), sourceCompressor(nullptr), @@ -147,12 +147,6 @@ BytecodeCompiler::maybeSetSourceCompressor(SourceCompressionTask* sourceCompress this->sourceCompressor = sourceCompressor; } -void -BytecodeCompiler::setSourceArgumentsNotIncluded() -{ - sourceArgumentsNotIncluded = true; -} - bool BytecodeCompiler::checkLength() { @@ -169,12 +163,12 @@ BytecodeCompiler::checkLength() } bool -BytecodeCompiler::createScriptSource() +BytecodeCompiler::createScriptSource(Maybe<uint32_t> parameterListEnd) { if (!checkLength()) return false; - sourceObject = CreateScriptSourceObject(cx, options); + sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd); if (!sourceObject) return false; @@ -193,9 +187,7 @@ BytecodeCompiler::maybeCompressSource() if (!cx->compartment()->behaviors().discardSource()) { if (options.sourceIsLazy) { scriptSource->setSourceRetrievable(); - } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceArgumentsNotIncluded, - sourceCompressor)) - { + } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceCompressor)) { return false; } } @@ -242,9 +234,9 @@ BytecodeCompiler::createParser() } bool -BytecodeCompiler::createSourceAndParser() +BytecodeCompiler::createSourceAndParser(Maybe<uint32_t> parameterListEnd /* = Nothing() */) { - return createScriptSource() && + return createScriptSource(parameterListEnd) && maybeCompressSource() && createParser(); } @@ -432,18 +424,19 @@ BytecodeCompiler::compileModule() return module; } +// Compile a standalone JS function, which might appear as the value of an +// event handler attribute in an HTML <INPUT> tag, or in a Function() +// constructor. bool -BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, - Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind) +BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Maybe<uint32_t> parameterListEnd) { MOZ_ASSERT(fun); MOZ_ASSERT(fun->isTenured()); - fun->setArgCount(formals.length()); - - if (!createSourceAndParser()) + if (!createSourceAndParser(parameterListEnd)) return false; // Speculatively parse using the default directives implied by the context. @@ -454,8 +447,8 @@ BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, ParseNode* fn; do { Directives newDirectives = directives; - fn = parser->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind, asyncKind, - directives, &newDirectives); + fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind, + asyncKind, directives, &newDirectives); if (!fn && !handleParseFailure(newDirectives)) return false; } while (!fn); @@ -492,14 +485,15 @@ BytecodeCompiler::sourceObjectPtr() const } ScriptSourceObject* -frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) +frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + Maybe<uint32_t> parameterListEnd /* = Nothing() */) { ScriptSource* ss = cx->new_<ScriptSource>(); if (!ss) return nullptr; ScriptSourceHolder ssHolder(ss); - if (!ss->initFromOptions(cx, options)) + if (!ss->initFromOptions(cx, options, parameterListEnd)) return nullptr; RootedScriptSource sso(cx, ScriptSourceObject::create(cx, ss)); @@ -676,63 +670,44 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha return bce.emitFunctionScript(pn->pn_body); } -// Compile a JS function body, which might appear as the value of an event -// handler attribute in an HTML <INPUT> tag, or in a Function() constructor. -static bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, SourceBufferHolder& srcBuf, - HandleScope enclosingScope, GeneratorKind generatorKind, - FunctionAsyncKind asyncKind) +bool +frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd, + HandleScope enclosingScope /* = nullptr */) { - MOZ_ASSERT(!options.isRunOnce); - - // FIXME: make Function pass in two strings and parse them as arguments and - // ProgramElements respectively. + RootedScope scope(cx, enclosingScope); + if (!scope) + scope = &cx->global()->emptyGlobalScope(); - BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, enclosingScope, + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, scope, TraceLogger_ParserCompileFunction); - compiler.setSourceArgumentsNotIncluded(); - return compiler.compileFunctionBody(fun, formals, generatorKind, asyncKind); + return compiler.compileStandaloneFunction(fun, NotGenerator, SyncFunction, parameterListEnd); } bool -frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf, - HandleScope enclosingScope) -{ - return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator, - SyncFunction); -} - -bool -frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf) +frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd) { RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - NotGenerator, SyncFunction); -} -bool -frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, - JS::SourceBufferHolder& srcBuf) -{ - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - StarGenerator, SyncFunction); + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, SyncFunction, parameterListEnd); } bool -frontend::CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, - JS::SourceBufferHolder& srcBuf) +frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd) { RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - StarGenerator, AsyncFunction); + + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd); } diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 1d86f1160..72e967639 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -7,6 +7,8 @@ #ifndef frontend_BytecodeCompiler_h #define frontend_BytecodeCompiler_h +#include "mozilla/Maybe.h" + #include "NamespaceImports.h" #include "vm/Scope.h" @@ -51,22 +53,36 @@ CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, MOZ_MUST_USE bool CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length); +// +// Compile a single function. The source in srcBuf must match the ECMA-262 +// FunctionExpression production. +// +// If nonzero, parameterListEnd is the offset within srcBuf where the parameter +// list is expected to end. During parsing, if we find that it ends anywhere +// else, it's a SyntaxError. This is used to implement the Function constructor; +// it's how we detect that these weird cases are SyntaxErrors: +// +// Function("/*", "*/x) {") +// Function("x){ if (3", "return x;}") +// MOZ_MUST_USE bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf, - HandleScope enclosingScope); +CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd, + HandleScope enclosingScope = nullptr); -// As above, but defaults to the global lexical scope as the enclosing scope. MOZ_MUST_USE bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); +CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd); MOZ_MUST_USE bool -CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); +CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd); MOZ_MUST_USE bool CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, @@ -74,7 +90,8 @@ CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); ScriptSourceObject* -CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options); +CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing()); /* * True if str consists of an IdentifierStart character, followed by one or diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 49fef2bf9..f42546eb5 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2245,13 +2245,13 @@ GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind) template <> ParseNode* -Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun, - HandleScope enclosingScope, - Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - Directives inheritedDirectives, - Directives* newDirectives) +Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, + HandleScope enclosingScope, + Maybe<uint32_t> parameterListEnd, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Directives inheritedDirectives, + Directives* newDirectives) { MOZ_ASSERT(checkOptionsCalled); @@ -2274,25 +2274,14 @@ Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun, if (!funpc.init()) return null(); funpc.setIsStandaloneFunctionBody(); - funpc.functionScope().useAsVarScope(&funpc); - - if (formals.length() >= ARGNO_LIMIT) { - report(ParseError, false, null(), JSMSG_TOO_MANY_FUN_ARGS); - return null(); - } - - bool duplicatedParam = false; - for (uint32_t i = 0; i < formals.length(); i++) { - if (!notePositionalFormalParameter(fn, formals[i], false, &duplicatedParam)) - return null(); - } - funbox->hasDuplicateParameters = duplicatedParam; YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind); AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction); - ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody); - if (!pn) + if (!functionFormalParametersAndBody(InAllowed, yieldHandling, fn, Statement, + parameterListEnd)) + { return null(); + } TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) @@ -2303,15 +2292,7 @@ Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun, return null(); } - if (!FoldConstants(context, &pn, this)) - return null(); - - fn->pn_pos.end = pos().end; - - MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY)); - fn->pn_body->append(pn); - - if (!finishFunction()) + if (!FoldConstants(context, &fn, this)) return null(); return fn; @@ -3415,7 +3396,8 @@ template <typename ParseHandler> bool Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, - Node pn, FunctionSyntaxKind kind) + Node pn, FunctionSyntaxKind kind, + Maybe<uint32_t> parameterListEnd /* = Nothing() */) { // Given a properly initialized parse context, try to parse an actual // function without concern for conversion to strict mode, use of lazy @@ -3447,6 +3429,13 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, } } + // When parsing something for new Function() we have to make sure to + // only treat a certain part of the source as a parameter list. + if (parameterListEnd.isSome() && parameterListEnd.value() != pos().begin) { + report(ParseError, false, null(), JSMSG_UNEXPECTED_PARAMLIST_END); + return false; + } + // Parse the function body. FunctionBodyType bodyType = StatementListBody; TokenKind tt; @@ -3502,7 +3491,7 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY); return false; } - funbox->bufEnd = pos().begin + 1; + funbox->bufEnd = pos().end; } else { #if !JS_HAS_EXPR_CLOSURES MOZ_ASSERT(kind == Arrow); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 0ad4d56a0..b58b021cd 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1020,12 +1020,12 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // Parse a module. Node moduleBody(ModuleSharedContext* modulesc); - // Parse a function, given only its body. Used for the Function and - // Generator constructors. - Node standaloneFunctionBody(HandleFunction fun, HandleScope enclosingScope, - Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - Directives inheritedDirectives, Directives* newDirectives); + // Parse a function, used for the Function, GeneratorFunction, and + // AsyncFunction constructors. + Node standaloneFunction(HandleFunction fun, HandleScope enclosingScope, + mozilla::Maybe<uint32_t> parameterListEnd, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + Directives inheritedDirectives, Directives* newDirectives); // Parse a function, given only its arguments and body. Used for lazily // parsed functions. @@ -1041,7 +1041,9 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // Parse a function's formal parameters and its body assuming its function // ParseContext is already on the stack. bool functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, - Node pn, FunctionSyntaxKind kind); + Node pn, FunctionSyntaxKind kind, + mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing()); + // Determine whether |yield| is a valid name in the current context, or // whether it's prohibited due to strictness, JS version, or occurrence diff --git a/js/src/jit-test/tests/basic/destructuring-default.js b/js/src/jit-test/tests/basic/destructuring-default.js index 168977e80..49a908b8a 100644 --- a/js/src/jit-test/tests/basic/destructuring-default.js +++ b/js/src/jit-test/tests/basic/destructuring-default.js @@ -84,10 +84,7 @@ function testArgumentFunction(pattern, input) { 'return [a, b, c, d, e, f];' )(input); } -// XXX: ES6 requires the `Function` constructor to accept arbitrary -// `BindingElement`s as formal parameters. See Bug 1037939. -// Once fixed, please update the assertions below. -assertThrowsInstanceOf(() => testAll(testArgumentFunction), SyntaxError); +testAll(testArgumentFunction); function testThrow(pattern, input) { return new Function('input', diff --git a/js/src/jit-test/tests/basic/destructuring-rest.js b/js/src/jit-test/tests/basic/destructuring-rest.js index f53f07e03..fcb7b79bb 100644 --- a/js/src/jit-test/tests/basic/destructuring-rest.js +++ b/js/src/jit-test/tests/basic/destructuring-rest.js @@ -132,10 +132,9 @@ function testArgumentFunction(pattern, input, binding) { 'return ' + binding )(input); } -// XXX: ES6 requires the `Function` constructor to accept arbitrary -// `BindingElement`s as formal parameters. See Bug 1037939. -// Once fixed, please update the assertions below. -assertThrowsInstanceOf(() => testDeclaration(testArgumentFunction), SyntaxError); +// ES6 requires the `Function` constructor to accept arbitrary +// `BindingElement`s as formal parameters. +testDeclaration(testArgumentFunction); function testThrow(pattern, input, binding) { binding = binding || 'rest'; diff --git a/js/src/jit-test/tests/debug/Script-gc-02.js b/js/src/jit-test/tests/debug/Script-gc-02.js index 33d33dfc1..04dd4b220 100644 --- a/js/src/jit-test/tests/debug/Script-gc-02.js +++ b/js/src/jit-test/tests/debug/Script-gc-02.js @@ -10,5 +10,5 @@ assertEq(arr.length, 10); gc(); for (var i = 0; i < arr.length; i++) - assertEq(arr[i].lineCount, 1); + assertEq(arr[i].lineCount, 3); diff --git a/js/src/jit-test/tests/debug/Script-gc-03.js b/js/src/jit-test/tests/debug/Script-gc-03.js index b2cb70232..30c3e8dbc 100644 --- a/js/src/jit-test/tests/debug/Script-gc-03.js +++ b/js/src/jit-test/tests/debug/Script-gc-03.js @@ -10,6 +10,6 @@ assertEq(arr.length, 100); gc(g); for (var i = 0; i < arr.length; i++) - assertEq(arr[i].lineCount, 1); + assertEq(arr[i].lineCount, 3); gc(); diff --git a/js/src/jit-test/tests/debug/Script-sourceStart-04.js b/js/src/jit-test/tests/debug/Script-sourceStart-04.js index c12e669bf..2aa382b7b 100644 --- a/js/src/jit-test/tests/debug/Script-sourceStart-04.js +++ b/js/src/jit-test/tests/debug/Script-sourceStart-04.js @@ -20,6 +20,6 @@ function test(string, range) { } test("eval('2 * 3')", [0, 5]); -test("new Function('2 * 3')", [0, 5]); -test("new Function('x', 'x * x')", [0, 5]); +test("new Function('2 * 3')", [0, 12]); +test("new Function('x', 'x * x')", [0, 13]); assertEq(count, 6); diff --git a/js/src/js.msg b/js/src/js.msg index 2a818056f..8766bfbf5 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -329,6 +329,7 @@ MSG_DEF(JSMSG_TOO_MANY_LOCALS, 0, JSEXN_SYNTAXERR, "too many local varia MSG_DEF(JSMSG_TOO_MANY_YIELDS, 0, JSEXN_SYNTAXERR, "too many yield expressions") MSG_DEF(JSMSG_TOUGH_BREAK, 0, JSEXN_SYNTAXERR, "unlabeled break must be inside loop or switch") MSG_DEF(JSMSG_UNEXPECTED_TOKEN, 2, JSEXN_SYNTAXERR, "expected {0}, got {1}") +MSG_DEF(JSMSG_UNEXPECTED_PARAMLIST_END,0, JSEXN_SYNTAXERR, "unexpected end of function parameter list") MSG_DEF(JSMSG_UNNAMED_CLASS_STMT, 0, JSEXN_SYNTAXERR, "class statement requires a name") MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name") MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 0, JSEXN_SYNTAXERR, "unterminated comment") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ee9c61059..e6fc1f98b 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -11,6 +11,7 @@ #include "jsapi.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Sprintf.h" @@ -107,6 +108,7 @@ using namespace js::gc; using mozilla::Maybe; using mozilla::PodCopy; using mozilla::PodZero; +using mozilla::Some; using JS::AutoGCRooter; using JS::ToInt32; @@ -4232,15 +4234,15 @@ JS_GetFunctionScript(JSContext* cx, HandleFunction fun) } /* - * enclosingScope is a static enclosing scope, if any (e.g. a WithScope). If - * the enclosing scope is the global scope, this must be null. + * enclosingScope is a scope, if any (e.g. a WithScope). If the scope is the + * global scope, this must be null. * - * enclosingDynamicScope is a dynamic scope to use, if it's not the global. + * enclosingEnv is an environment to use, if it's not the global. */ static bool CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, - const char* name, unsigned nargs, const char* const* argnames, - SourceBufferHolder& srcBuf, + const char* name, + SourceBufferHolder& srcBuf, uint32_t parameterListEnd, HandleObject enclosingEnv, HandleScope enclosingScope, MutableHandleFunction fun) { @@ -4256,13 +4258,6 @@ CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, return false; } - Rooted<PropertyNameVector> formals(cx, PropertyNameVector(cx)); - for (unsigned i = 0; i < nargs; i++) { - RootedAtom argAtom(cx, Atomize(cx, argnames[i], strlen(argnames[i]))); - if (!argAtom || !formals.append(argAtom->asPropertyName())) - return false; - } - fun.set(NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL, funAtom, /* proto = */ nullptr, gc::AllocKind::FUNCTION, TenuredObject, @@ -4275,7 +4270,45 @@ CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& optionsArg, MOZ_ASSERT_IF(!IsGlobalLexicalEnvironment(enclosingEnv), enclosingScope->hasOnChain(ScopeKind::NonSyntactic)); - if (!frontend::CompileFunctionBody(cx, fun, optionsArg, formals, srcBuf, enclosingScope)) + if (!frontend::CompileStandaloneFunction(cx, fun, optionsArg, srcBuf, + Some(parameterListEnd), enclosingScope)) + { + return false; + } + + return true; +} + +static MOZ_MUST_USE bool +BuildFunctionString(unsigned nargs, const char* const* argnames, + const SourceBufferHolder& srcBuf, StringBuffer* out, + uint32_t* parameterListEnd) +{ + MOZ_ASSERT(out); + MOZ_ASSERT(parameterListEnd); + + if (!out->ensureTwoByteChars()) + return false; + if (!out->append("(")) + return false; + for (unsigned i = 0; i < nargs; i++) { + if (i != 0) { + if (!out->append(", ")) + return false; + } + if (!out->append(argnames[i], strlen(argnames[i]))) + return false; + } + + // Remember the position of ")". + *parameterListEnd = out->length(); + MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')'); + + if (!out->append(FunctionConstructorMedialSigils)) + return false; + if (!out->append(srcBuf.get(), srcBuf.length())) + return false; + if (!out->append(FunctionConstructorFinalBrace)) return false; return true; @@ -4291,7 +4324,16 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, RootedScope scope(cx); if (!CreateNonSyntacticEnvironmentChain(cx, envChain, &env, &scope)) return false; - return CompileFunction(cx, options, name, nargs, argnames, srcBuf, env, scope, fun); + + uint32_t parameterListEnd; + StringBuffer funStr(cx); + if (!BuildFunctionString(nargs, argnames, srcBuf, &funStr, ¶meterListEnd)) + return false; + + size_t newLen = funStr.length(); + SourceBufferHolder newSrcBuf(funStr.stealChars(), newLen, SourceBufferHolder::GiveOwnership); + + return CompileFunction(cx, options, name, newSrcBuf, parameterListEnd, env, scope, fun); } JS_PUBLIC_API(bool) 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; diff --git a/js/src/jsfun.h b/js/src/jsfun.h index ef4a603d0..88af5c22d 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -28,6 +28,9 @@ static const uint32_t JSSLOT_BOUND_FUNCTION_TARGET = 2; static const uint32_t JSSLOT_BOUND_FUNCTION_THIS = 3; static const uint32_t JSSLOT_BOUND_FUNCTION_ARGS = 4; +static const char FunctionConstructorMedialSigils[] = ") {\n"; +static const char FunctionConstructorFinalBrace[] = "\n}"; + class JSFunction : public js::NativeObject { public: @@ -816,7 +819,7 @@ JSFunction::getExtendedSlot(size_t which) const namespace js { -JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen); +JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPring); template<XDRMode mode> bool diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 10821f26a..b568b4b30 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1689,6 +1689,16 @@ ScriptSource::substringDontDeflate(JSContext* cx, size_t start, size_t stop) return NewStringCopyNDontDeflate<CanGC>(cx, chars, len); } +JSFlatString* +ScriptSource::functionBodyString(JSContext* cx) +{ + MOZ_ASSERT(isFunctionBody()); + + size_t start = parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1); + size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1); + return substring(cx, start, stop); +} + MOZ_MUST_USE bool ScriptSource::setSource(ExclusiveContext* cx, mozilla::UniquePtr<char16_t[], JS::FreePolicy>&& source, @@ -1740,10 +1750,9 @@ ScriptSource::setCompressedSource(SharedImmutableString&& raw, size_t uncompress bool ScriptSource::setSourceCopy(ExclusiveContext* cx, SourceBufferHolder& srcBuf, - bool argumentsNotIncluded, SourceCompressionTask* task) + SourceCompressionTask* task) { MOZ_ASSERT(!hasSourceData()); - argumentsNotIncluded_ = argumentsNotIncluded; auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings(); auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&]() { @@ -1940,16 +1949,6 @@ ScriptSource::performXDR(XDRState<mode>* xdr) if (!xdr->codeUint32(&compressedLength)) return false; - { - uint8_t argumentsNotIncluded; - if (mode == XDR_ENCODE) - argumentsNotIncluded = argumentsNotIncluded_; - if (!xdr->codeUint8(&argumentsNotIncluded)) - return false; - if (mode == XDR_DECODE) - argumentsNotIncluded_ = argumentsNotIncluded; - } - size_t byteLen = compressedLength ? compressedLength : (len * sizeof(char16_t)); if (mode == XDR_DECODE) { uint8_t* p = xdr->cx()->template pod_malloc<uint8_t>(Max<size_t>(byteLen, 1)); @@ -2074,7 +2073,8 @@ FormatIntroducedFilename(ExclusiveContext* cx, const char* filename, unsigned li } bool -ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) +ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + Maybe<uint32_t> parameterListEnd) { MOZ_ASSERT(!filename_); MOZ_ASSERT(!introducerFilename_); @@ -2083,6 +2083,7 @@ ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions introductionType_ = options.introductionType; setIntroductionOffset(options.introductionOffset); + parameterListEnd_ = parameterListEnd.isSome() ? parameterListEnd.value() : 0; if (options.hasIntroductionInfo) { MOZ_ASSERT(options.introductionType != nullptr); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index bc8bda83d..ffd4b1e2e 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -10,6 +10,7 @@ #define jsscript_h #include "mozilla/Atomics.h" +#include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Variant.h" @@ -399,6 +400,11 @@ class ScriptSource // scripts. uint32_t introductionOffset_; + // If this source is for Function constructor, the position of ")" after + // parameter list in the source. This is used to get function body. + // 0 for other cases. + uint32_t parameterListEnd_; + // If this ScriptSource was generated by a code-introduction mechanism such // as |eval| or |new Function|, the debugger needs access to the "raw" // filename of the top-level script that contains the eval-ing code. To @@ -428,7 +434,6 @@ class ScriptSource // demand. If sourceRetrievable_ and hasSourceData() are false, it is not // possible to get source at all. bool sourceRetrievable_:1; - bool argumentsNotIncluded_:1; bool hasIntroductionOffset_:1; const char16_t* chunkChars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder, @@ -443,10 +448,10 @@ class ScriptSource sourceMapURL_(nullptr), mutedErrors_(false), introductionOffset_(0), + parameterListEnd_(0), introducerFilename_(nullptr), introductionType_(nullptr), sourceRetrievable_(false), - argumentsNotIncluded_(false), hasIntroductionOffset_(false) { } @@ -461,10 +466,10 @@ class ScriptSource if (--refs == 0) js_delete(this); } - bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options); + bool initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing()); bool setSourceCopy(ExclusiveContext* cx, JS::SourceBufferHolder& srcBuf, - bool argumentsNotIncluded, SourceCompressionTask* tok); void setSourceRetrievable() { sourceRetrievable_ = true; } bool sourceRetrievable() const { return sourceRetrievable_; } @@ -492,11 +497,6 @@ class ScriptSource return data.match(LengthMatcher()); } - bool argumentsNotIncluded() const { - MOZ_ASSERT(hasSourceData()); - return argumentsNotIncluded_; - } - // Return a string containing the chars starting at |begin| and ending at // |begin + len|. const char16_t* chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp, @@ -504,6 +504,12 @@ class ScriptSource JSFlatString* substring(JSContext* cx, size_t start, size_t stop); JSFlatString* substringDontDeflate(JSContext* cx, size_t start, size_t stop); + + bool isFunctionBody() { + return parameterListEnd_ != 0; + } + JSFlatString* functionBodyString(JSContext* cx); + void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ScriptSourceInfo* info) const; @@ -567,6 +573,10 @@ class ScriptSource introductionOffset_ = offset; hasIntroductionOffset_ = true; } + + uint32_t parameterListEnd() const { + return parameterListEnd_; + } }; class ScriptSourceHolder diff --git a/js/src/tests/Intl/Collator/construct-newtarget.js b/js/src/tests/Intl/Collator/construct-newtarget.js new file mode 100644 index 000000000..5db1abf37 --- /dev/null +++ b/js/src/tests/Intl/Collator/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* 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/. */ + + +// Test subclassing %Intl.Collator% works correctly. +class MyCollator extends Intl.Collator {} + +var obj = new MyCollator(); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, []); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, [], MyCollator); +assertEq(obj instanceof MyCollator, true); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), MyCollator.prototype); + +obj = Reflect.construct(MyCollator, [], Intl.Collator); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyCollator, [], Array); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.Collator, [], Array); +assertEq(obj instanceof Intl.Collator, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %CollatorPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.Collator, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + +obj = Reflect.construct(MyCollator, [], NewTargetNullPrototype); +assertEq(obj instanceof MyCollator, false); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.Collator, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.Collator, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.Collator, true); +assertEq(Object.getPrototypeOf(obj), Intl.Collator.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js new file mode 100644 index 000000000..bbc08c79a --- /dev/null +++ b/js/src/tests/Intl/DateTimeFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* 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/. */ + + +// Test subclassing %Intl.DateTimeFormat% works correctly. +class MyDateTimeFormat extends Intl.DateTimeFormat {} + +var obj = new MyDateTimeFormat(); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, []); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], MyDateTimeFormat); +assertEq(obj instanceof MyDateTimeFormat, true); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), MyDateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], Intl.DateTimeFormat); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyDateTimeFormat, [], Array); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.DateTimeFormat, [], Array); +assertEq(obj instanceof Intl.DateTimeFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %DateTimeFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.DateTimeFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + +obj = Reflect.construct(MyDateTimeFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyDateTimeFormat, false); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.DateTimeFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.DateTimeFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.DateTimeFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.DateTimeFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/Intl/NumberFormat/construct-newtarget.js b/js/src/tests/Intl/NumberFormat/construct-newtarget.js new file mode 100644 index 000000000..0053b2737 --- /dev/null +++ b/js/src/tests/Intl/NumberFormat/construct-newtarget.js @@ -0,0 +1,81 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")) + +/* 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/. */ + + +// Test subclassing %Intl.NumberFormat% works correctly. +class MyNumberFormat extends Intl.NumberFormat {} + +var obj = new MyNumberFormat(); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, []); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], MyNumberFormat); +assertEq(obj instanceof MyNumberFormat, true); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), MyNumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], Intl.NumberFormat); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// Set a different constructor as NewTarget. +obj = Reflect.construct(MyNumberFormat, [], Array); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + +obj = Reflect.construct(Intl.NumberFormat, [], Array); +assertEq(obj instanceof Intl.NumberFormat, false); +assertEq(obj instanceof Array, true); +assertEq(Object.getPrototypeOf(obj), Array.prototype); + + +// The prototype defaults to %NumberFormatPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +obj = Reflect.construct(Intl.NumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + +obj = Reflect.construct(MyNumberFormat, [], NewTargetNullPrototype); +assertEq(obj instanceof MyNumberFormat, false); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(Intl.NumberFormat, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +obj = Reflect.construct(Intl.NumberFormat, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(obj instanceof Intl.NumberFormat, true); +assertEq(Object.getPrototypeOf(obj), Intl.NumberFormat.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js new file mode 100644 index 000000000..7d75d0c93 --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/construct-newtarget.js @@ -0,0 +1,79 @@ +/* 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/. */ + +const AsyncFunction = async function(){}.constructor; + + +// Test subclassing %AsyncFunction% works correctly. +class MyAsync extends AsyncFunction {} + +var fn = new MyAsync(); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, []); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], MyAsync); +assertEq(fn instanceof MyAsync, true); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +fn = Reflect.construct(MyAsync, [], AsyncFunction); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyAsync, [], Array); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(AsyncFunction, [], Array); +assertEq(fn instanceof AsyncFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %AsyncFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(AsyncFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + +fn = Reflect.construct(MyAsync, [], NewTargetNullPrototype); +assertEq(fn instanceof MyAsync, false); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(AsyncFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(AsyncFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof AsyncFunction, true); +assertEq(Object.getPrototypeOf(fn), AsyncFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_2017/AsyncFunctions/subclass.js b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js new file mode 100644 index 000000000..da20ab19b --- /dev/null +++ b/js/src/tests/ecma_2017/AsyncFunctions/subclass.js @@ -0,0 +1,31 @@ +// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue +/* 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/. */ + +const AsyncFunction = async function(){}.constructor; + +class MyAsync extends AsyncFunction {} + +// MyGen inherits from %AsyncFunction%. +assertEq(Object.getPrototypeOf(MyAsync), AsyncFunction); + +// MyGen.prototype inherits from %AsyncFunctionPrototype%. +assertEq(Object.getPrototypeOf(MyAsync.prototype), AsyncFunction.prototype); + +var fn = new MyAsync("return await 'ok';"); + +// fn inherits from MyAsync.prototype. +assertEq(Object.getPrototypeOf(fn), MyAsync.prototype); + +// Ensure the new async function can be executed. +var promise = fn(); + +// promise inherits from %Promise.prototype%. +assertEq(Object.getPrototypeOf(promise), Promise.prototype); + +// Computes the expected result. +assertEventuallyEq(promise, "ok"); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Function/invalid-parameter-list.js b/js/src/tests/ecma_6/Function/invalid-parameter-list.js new file mode 100644 index 000000000..8aae89ef1 --- /dev/null +++ b/js/src/tests/ecma_6/Function/invalid-parameter-list.js @@ -0,0 +1,27 @@ +// This constructor behaves like `Function` without checking +// if the parameter list end is at the expected position. +// We use this to make sure that the tests we use are otherwise +// syntactically correct. +function DumpFunction(...args) { + let code = "function anonymous("; + code += args.slice(0, -1).join(", "); + code += ") {\n"; + code += args[args.length -1]; + code += "\n}"; + eval(code); +} + +const tests = [ + ["/*", "*/) {"], + ["//", ") {"], + ["a = `", "` ) {"], + [") { var x = function (", "} "], + ["x = function (", "}) {"] +]; + +for (const test of tests) { + DumpFunction(...test); + assertThrowsInstanceOf(() => new Function(...test), SyntaxError); +} + +reportCompare(0, 0, 'ok'); diff --git a/js/src/tests/ecma_6/Generators/construct-newtarget.js b/js/src/tests/ecma_6/Generators/construct-newtarget.js new file mode 100644 index 000000000..f2087852b --- /dev/null +++ b/js/src/tests/ecma_6/Generators/construct-newtarget.js @@ -0,0 +1,79 @@ +/* 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/. */ + +const GeneratorFunction = function*(){}.constructor; + + +// Test subclassing %GeneratorFunction% works correctly. +class MyGenerator extends GeneratorFunction {} + +var fn = new MyGenerator(); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, []); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, [], MyGenerator); +assertEq(fn instanceof MyGenerator, true); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), MyGenerator.prototype); + +fn = Reflect.construct(MyGenerator, [], GeneratorFunction); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +// Set a different constructor as NewTarget. +fn = Reflect.construct(MyGenerator, [], Array); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + +fn = Reflect.construct(GeneratorFunction, [], Array); +assertEq(fn instanceof GeneratorFunction, false); +assertEq(Object.getPrototypeOf(fn), Array.prototype); + + +// The prototype defaults to %GeneratorFunctionPrototype% if null. +function NewTargetNullPrototype() {} +NewTargetNullPrototype.prototype = null; + +fn = Reflect.construct(GeneratorFunction, [], NewTargetNullPrototype); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + +fn = Reflect.construct(MyGenerator, [], NewTargetNullPrototype); +assertEq(fn instanceof MyGenerator, false); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +// "prototype" property is retrieved exactly once. +var trapLog = [], getLog = []; +var ProxiedConstructor = new Proxy(GeneratorFunction, new Proxy({ + get(target, propertyKey, receiver) { + getLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +}, { + get(target, propertyKey, receiver) { + trapLog.push(propertyKey); + return Reflect.get(target, propertyKey, receiver); + } +})); + +fn = Reflect.construct(GeneratorFunction, [], ProxiedConstructor); +assertEqArray(trapLog, ["get"]); +assertEqArray(getLog, ["prototype"]); +assertEq(fn instanceof GeneratorFunction, true); +assertEq(Object.getPrototypeOf(fn), GeneratorFunction.prototype); + + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Generators/subclass.js b/js/src/tests/ecma_6/Generators/subclass.js new file mode 100644 index 000000000..f93f4df4d --- /dev/null +++ b/js/src/tests/ecma_6/Generators/subclass.js @@ -0,0 +1,33 @@ +/* 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/. */ + +const GeneratorFunction = function*(){}.constructor; + +class MyGen extends GeneratorFunction {} + +// MyGen inherits from %GeneratorFunction%. +assertEq(Object.getPrototypeOf(MyGen), GeneratorFunction); + +// MyGen.prototype inherits from %Generator%. +assertEq(Object.getPrototypeOf(MyGen.prototype), GeneratorFunction.prototype); + +var fn = new MyGen("yield* [1, 2, 3]"); + +// fn inherits from MyGen.prototype. +assertEq(Object.getPrototypeOf(fn), MyGen.prototype); + +// fn.prototype inherits from %GeneratorPrototype%. +assertEq(Object.getPrototypeOf(fn.prototype), GeneratorFunction.prototype.prototype); + +// Ensure the new generator function can be executed. +var it = fn(); + +// it inherits from fn.prototype. +assertEq(Object.getPrototypeOf(it), fn.prototype); + +// Computes the expected result. +assertEqArray([...it], [1, 2, 3]); + +if (typeof reportCompare === "function") + reportCompare(0, 0); diff --git a/js/src/vm/AsyncFunction.cpp b/js/src/vm/AsyncFunction.cpp index bd0b4f32a..1e0c7d7c2 100644 --- a/js/src/vm/AsyncFunction.cpp +++ b/js/src/vm/AsyncFunction.cpp @@ -107,18 +107,15 @@ WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp) // the async function's body, replacing `await` with `yield`. `wrapped` is a // function that is visible to the outside, and handles yielded values. JSObject* -js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) +js::WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto) { MOZ_ASSERT(unwrapped->isStarGenerator()); + MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default" + "%FunctionPrototype% fallback in NewFunctionWithProto()."); // Create a new function with AsyncFunctionPrototype, reusing the name and // the length of `unwrapped`. - // Step 1. - RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global())); - if (!proto) - return nullptr; - RootedAtom funName(cx, unwrapped->name()); uint16_t length; if (!unwrapped->getLength(cx, &length)) @@ -141,6 +138,16 @@ js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) return wrapped; } +JSObject* +js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped) +{ + RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global())); + if (!proto) + return nullptr; + + return WrapAsyncFunctionWithProto(cx, unwrapped, proto); +} + enum class ResumeKind { Normal, Throw diff --git a/js/src/vm/AsyncFunction.h b/js/src/vm/AsyncFunction.h index ddf81a177..d7f2c1311 100644 --- a/js/src/vm/AsyncFunction.h +++ b/js/src/vm/AsyncFunction.h @@ -22,6 +22,9 @@ bool IsWrappedAsyncFunction(JSFunction* fun); JSObject* +WrapAsyncFunctionWithProto(JSContext* cx, HandleFunction unwrapped, HandleObject proto); + +JSObject* WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped); MOZ_MUST_USE bool diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index b6bc7d62a..4d181545f 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -6890,8 +6890,13 @@ class DebuggerSourceGetTextMatcher bool hasSourceData = ss->hasSourceData(); if (!ss->hasSourceData() && !JSScript::loadSource(cx_, ss, &hasSourceData)) return nullptr; - return hasSourceData ? ss->substring(cx_, 0, ss->length()) - : NewStringCopyZ<CanGC>(cx_, "[no source]"); + if (!hasSourceData) + return NewStringCopyZ<CanGC>(cx_, "[no source]"); + + if (ss->isFunctionBody()) + return ss->functionBodyString(cx_); + + return ss->substring(cx_, 0, ss->length()); } ReturnType match(Handle<WasmInstanceObject*> wasmInstance) { diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 34f5a8c0d..4dbc9b387 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -21,6 +21,7 @@ #include "mozilla/Attributes.h" #include "mozilla/Compression.h" #include "mozilla/MathAlgorithms.h" +#include "mozilla/Maybe.h" #include "jsmath.h" #include "jsprf.h" @@ -8033,21 +8034,6 @@ TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata return true; } -static MOZ_MUST_USE bool -MaybeAppendUTF8Name(JSContext* cx, const char* utf8Chars, MutableHandle<PropertyNameVector> names) -{ - if (!utf8Chars) - return true; - - UTF8Chars utf8(utf8Chars, strlen(utf8Chars)); - - JSAtom* atom = AtomizeUTF8Chars(cx, utf8Chars, strlen(utf8Chars)); - if (!atom) - return false; - - return names.append(atom->asPropertyName()); -} - static bool HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata) { @@ -8068,8 +8054,8 @@ HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& me return false; } - uint32_t begin = metadata.srcBodyStart; // starts right after 'use asm' - uint32_t end = metadata.srcEndBeforeCurly(); + uint32_t begin = metadata.srcStart; + uint32_t end = metadata.srcEndAfterCurly(); Rooted<JSFlatString*> src(cx, source->substringDontDeflate(cx, begin, end)); if (!src) return false; @@ -8080,18 +8066,11 @@ HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& me if (!fun) return false; - Rooted<PropertyNameVector> formals(cx, PropertyNameVector(cx)); - if (!MaybeAppendUTF8Name(cx, metadata.globalArgumentName.get(), &formals)) - return false; - if (!MaybeAppendUTF8Name(cx, metadata.importArgumentName.get(), &formals)) - return false; - if (!MaybeAppendUTF8Name(cx, metadata.bufferArgumentName.get(), &formals)) - return false; - CompileOptions options(cx); options.setMutedErrors(source->mutedErrors()) .setFile(source->filename()) .setNoScriptRval(false); + options.asmJSOption = AsmJSOption::Disabled; // The exported function inherits an implicit strict context if the module // also inherited it somehow. @@ -8106,8 +8085,8 @@ HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& me SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; - SourceBufferHolder srcBuf(chars, end - begin, ownership); - if (!frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf)) + SourceBufferHolder srcBuf(chars, stableChars.twoByteRange().length(), ownership); + if (!frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, Nothing())) return false; // Call the function we just recompiled. @@ -8849,23 +8828,6 @@ js::IsAsmJSModuleLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) /*****************************************************************************/ // asm.js toString/toSource support -static MOZ_MUST_USE bool -MaybeAppendUTF8Chars(JSContext* cx, const char* sep, const char* utf8Chars, StringBuffer* sb) -{ - if (!utf8Chars) - return true; - - UTF8Chars utf8(utf8Chars, strlen(utf8Chars)); - - size_t length; - UniqueTwoByteChars twoByteChars(UTF8CharsToNewTwoByteCharsZ(cx, utf8, &length).get()); - if (!twoByteChars) - return false; - - return sb->append(sep, strlen(sep)) && - sb->append(twoByteChars.get(), length); -} - JSString* js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda) { @@ -8895,33 +8857,12 @@ js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda if (!out.append("() {\n [sourceless code]\n}")) return nullptr; } else { - // Whether the function has been created with a Function ctor - bool funCtor = begin == 0 && end == source->length() && source->argumentsNotIncluded(); - if (funCtor) { - // Functions created with the function constructor don't have arguments in their source. - if (!out.append("(")) - return nullptr; - - if (!MaybeAppendUTF8Chars(cx, "", metadata.globalArgumentName.get(), &out)) - return nullptr; - if (!MaybeAppendUTF8Chars(cx, ", ", metadata.importArgumentName.get(), &out)) - return nullptr; - if (!MaybeAppendUTF8Chars(cx, ", ", metadata.bufferArgumentName.get(), &out)) - return nullptr; - - if (!out.append(") {\n")) - return nullptr; - } - Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end)); if (!src) return nullptr; if (!out.append(src)) return nullptr; - - if (funCtor && !out.append("\n}")) - return nullptr; } if (addParenToLambda && fun->isLambda() && !out.append(")")) @@ -8959,10 +8900,6 @@ js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) if (!out.append("() {\n [sourceless code]\n}")) return nullptr; } else { - // asm.js functions cannot have been created with a Function constructor - // as they belong within a module. - MOZ_ASSERT(!(begin == 0 && end == source->length() && source->argumentsNotIncluded())); - Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end)); if (!src) return nullptr; diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp index 824e9ab9e..9c8908ea4 100644 --- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -130,7 +130,9 @@ PrepareScript(nsIURI* uri, MutableHandleFunction function) { JS::CompileOptions options(cx); - options.setFileAndLine(uriStr, 1) + // Use line 0 to make the function body starts from line 1 when + // |reuseGlobal == true|. + options.setFileAndLine(uriStr, reuseGlobal ? 0 : 1) .setVersion(JSVERSION_LATEST); if (!charset.IsVoid()) { char16_t* scriptBuf = nullptr; |