diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 4aa7f1640b..0205887ff6 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -53,6 +53,8 @@ enum AssignmentOperator { AOP_LSH, AOP_RSH, AOP_URSH, /* binary */ AOP_BITOR, AOP_BITXOR, AOP_BITAND, + /* short-circuit */ + AOP_COALESCE, AOP_OR, AOP_AND, AOP_LIMIT }; @@ -122,6 +124,9 @@ static const char* const aopNames[] = { "|=", /* AOP_BITOR */ "^=", /* AOP_BITXOR */ "&=" /* AOP_BITAND */ + "\?\?=", /* AOP_COALESCE */ + "||=", /* AOP_OR */ + "&&=", /* AOP_AND */ }; static const char* const binopNames[] = { @@ -539,9 +544,12 @@ class NodeBuilder MOZ_MUST_USE bool classDefinition(bool expr, HandleValue name, HandleValue heritage, HandleValue block, TokenPos* pos, MutableHandleValue dst); - MOZ_MUST_USE bool classMethods(NodeVector& methods, MutableHandleValue dst); + MOZ_MUST_USE bool classMembers(NodeVector& members, MutableHandleValue dst); MOZ_MUST_USE bool classMethod(HandleValue name, HandleValue body, PropKind kind, bool isStatic, TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool classField(HandleValue name, HandleValue initializer, + TokenPos* pos, MutableHandleValue dst); + MOZ_MUST_USE bool staticClassBlock(HandleValue body, TokenPos* pos, MutableHandleValue dst); /* * expressions @@ -1721,9 +1729,35 @@ NodeBuilder::classMethod(HandleValue name, HandleValue body, PropKind kind, bool } bool -NodeBuilder::classMethods(NodeVector& methods, MutableHandleValue dst) +NodeBuilder::classField(HandleValue name, HandleValue initializer, + TokenPos* pos, MutableHandleValue dst) { - return newArray(methods, dst); + RootedValue cb(cx, callbacks[AST_CLASS_FIELD]); + if (!cb.isNull()) + return callback(cb, name, initializer, pos, dst); + + return newNode(AST_CLASS_FIELD, pos, + "name", name, + "init", initializer, + dst); +} + +bool +NodeBuilder::staticClassBlock(HandleValue body, TokenPos* pos, MutableHandleValue dst) +{ + RootedValue cb(cx, callbacks[AST_STATIC_CLASS_BLOCK]); + if (!cb.isNull()) + return callback(cb, body, pos, dst); + + return newNode(AST_STATIC_CLASS_BLOCK, pos, + "body", body, + dst); +} + +bool +NodeBuilder::classMembers(NodeVector& members, MutableHandleValue dst) +{ + return newArray(members, dst); } bool @@ -1853,6 +1887,8 @@ class ASTSerializer bool property(ParseNode* pn, MutableHandleValue dst); bool classMethod(ClassMethod* classMethod, MutableHandleValue dst); + bool classField(ClassField* classField, MutableHandleValue dst); + bool staticClassBlock(StaticClassBlock* staticClassBlock, MutableHandleValue dst); bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) { if (!atom) { @@ -1943,6 +1979,12 @@ ASTSerializer::aop(JSOp op) return AOP_BITXOR; case JSOP_BITAND: return AOP_BITAND; + case JSOP_COALESCE: + return AOP_COALESCE; + case JSOP_OR: + return AOP_OR; + case JSOP_AND: + return AOP_AND; default: return AOP_ERR; } @@ -2457,7 +2499,7 @@ ASTSerializer::classDefinition(ClassNode* pn, bool expr, MutableHandleValue dst) } return optExpression(pn->heritage(), &heritage) && - statement(pn->methodList(), &classBody) && + statement(pn->memberList(), &classBody) && builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst); } @@ -2676,24 +2718,43 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) case PNK_CLASS: return classDefinition(&pn->as(), false, dst); - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: { - ListNode* methodList = &pn->as(); - NodeVector methods(cx); - if (!methods.reserve(methodList->count())) + ListNode* memberList = &pn->as(); + NodeVector members(cx); + if (!members.reserve(memberList->count())) return false; - for (ParseNode* item : methodList->contents()) { - ClassMethod* method = &item->as(); - MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos)); + for (ParseNode* item : memberList->contents()) { + if (item->is()) + item = item->as().scopeBody(); + if (item->is()) { + ClassField* field = &item->as(); + MOZ_ASSERT(memberList->pn_pos.encloses(field->pn_pos)); - RootedValue prop(cx); - if (!classMethod(method, &prop)) - return false; - methods.infallibleAppend(prop); + RootedValue prop(cx); + if (!classField(field, &prop)) + return false; + members.infallibleAppend(prop); + } else if (item->is()) { + StaticClassBlock* scb = &item->as(); + MOZ_ASSERT(memberList->pn_pos.encloses(scb->pn_pos)); + RootedValue prop(cx); + if (!staticClassBlock(scb, &prop)) + return false; + members.infallibleAppend(prop); + } else { + ClassMethod* method = &item->as(); + MOZ_ASSERT(memberList->pn_pos.encloses(method->pn_pos)); + + RootedValue prop(cx); + if (!classMethod(method, &prop)) + return false; + members.infallibleAppend(prop); + } } - return builder.classMethods(methods, dst); + return builder.classMembers(members, dst); } case PNK_NOP: @@ -2732,6 +2793,47 @@ ASTSerializer::classMethod(ClassMethod* classMethod, MutableHandleValue dst) builder.classMethod(key, val, kind, isStatic, &classMethod->pn_pos, dst); } +bool +ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) +{ + RootedValue key(cx), val(cx); + // Dig through the lambda and get to the actual expression + ParseNode* value = classField->initializer() + ->body() + ->head()->as() + .scopeBody()->as() + .head()->as() + .kid()->as() + .right(); + // RawUndefinedExpr is the node we use for "there is no initializer". If one + // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr + // node, but rather a variable reference. + // Behavior for "there is no initializer" should be { ..., "init": null } + if (value->getKind() != PNK_RAW_UNDEFINED) { + if (!expression(value, &val)) + return false; + } else { + val.setNull(); + } + return propertyName(&classField->name(), &key) && + builder.classField(key, val, &classField->pn_pos, dst); +} + +bool +ASTSerializer::staticClassBlock(StaticClassBlock* staticClassBlock, MutableHandleValue dst) +{ + FunctionNode* fun = staticClassBlock->function(); + + NodeVector args(cx); + NodeVector defaults(cx); + + RootedValue body(cx), rest(cx); + rest.setNull(); + return functionArgsAndBody(fun->body(), args, defaults, false, false, + &body, &rest) && + builder.staticClassBlock(body, &staticClassBlock->pn_pos, dst); +} + bool ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst) { @@ -3012,6 +3114,9 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 67423eb7ab..7f5b705f92 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -676,8 +676,13 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha if (lazy->hasBeenCloned()) script->setHasBeenCloned(); + FieldInitializers fieldInitializers = FieldInitializers::Invalid(); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + fieldInitializers = lazy->getFieldInitializers(); + } + BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->as().funbox(), script, lazy, - pn->pn_pos, BytecodeEmitter::LazyFunction); + pn->pn_pos, BytecodeEmitter::LazyFunction, fieldInitializers); if (!bce.init()) return false; diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 6d03f8f9fb..0a4703ae1e 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -110,14 +110,22 @@ CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& opt bool IsIdentifier(JSLinearString* str); +bool +IsIdentifierNameOrPrivateName(JSLinearString* str); + /* * As above, but taking chars + length. */ bool -IsIdentifier(const char* chars, size_t length); +IsIdentifier(const Latin1Char* chars, size_t length); bool IsIdentifier(const char16_t* chars, size_t length); +static bool +IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length); +bool +IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length); + /* True if str is a keyword. Defined in TokenStream.cpp. */ bool IsKeyword(JSLinearString* str); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 51ebd81969..1e6390f5f1 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -30,11 +30,15 @@ #include "ds/Nestable.h" #include "frontend/BytecodeControlStructures.h" #include "frontend/CallOrNewEmitter.h" +#include "frontend/DefaultEmitter.h" // DefaultEmitter #include "frontend/ElemOpEmitter.h" #include "frontend/EmitterScope.h" #include "frontend/ForOfLoopControl.h" +#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter #include "frontend/IfEmitter.h" +#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter #include "frontend/NameOpEmitter.h" +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter #include "frontend/Parser.h" #include "frontend/PropOpEmitter.h" #include "frontend/SwitchEmitter.h" @@ -152,7 +156,8 @@ class MOZ_RAII OptionalEmitter BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, - uint32_t lineNum, EmitterMode emitterMode) + uint32_t lineNum, EmitterMode emitterMode, + FieldInitializers fieldInitializers /* = FieldInitializers::Invalid() */) : sc(sc), cx(sc->context), parent(parent), @@ -162,6 +167,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, main(cx, lineNum), current(&main), parser(parser), + fieldInitializers_(fieldInitializers), atomIndices(cx->frontendCollectionPool()), firstLine(lineNum), maxFixedSlots(0), @@ -186,8 +192,7 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, hasSingletons(false), hasTryFinally(false), emittingRunOnceLambda(false), - emitterMode(emitterMode), - functionBodyEndPosSet(false) + emitterMode(emitterMode) { MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript); } @@ -195,12 +200,14 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, - TokenPos bodyPosition, EmitterMode emitterMode) + TokenPos bodyPosition, EmitterMode emitterMode, + FieldInitializers fieldInitializers) : BytecodeEmitter(parent, parser, sc, script, lazyScript, parser->tokenStream.srcCoords.lineNum(bodyPosition.begin), - emitterMode) + emitterMode, fieldInitializers) { - setFunctionBodyEndPos(bodyPosition); + setScriptStartOffsetIfUnset(bodyPosition.begin); + setFunctionBodyEndPos(bodyPosition.end); } bool @@ -510,6 +517,17 @@ BytecodeEmitter::emitPopN(unsigned n) return emitUint16Operand(JSOP_POPN, n); } +bool +BytecodeEmitter::emitUnpickN(unsigned n) +{ + MOZ_ASSERT(n != 0); + + if (n == 1) + return emit1(JSOP_SWAP); + + return emit2(JSOP_UNPICK, n); +} + bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { @@ -1058,6 +1076,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) return true; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: // no side effects, unlike PNK_NAME case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(pn->is()); @@ -1206,9 +1225,16 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) return true; // Binary cases with obvious side effects. + case PNK_INITPROP: + *answer = true; + return true; + case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: @@ -1222,7 +1248,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) MOZ_ASSERT(pn->is()); *answer = true; return true; - + case PNK_SETTHIS: MOZ_ASSERT(pn->is()); *answer = true; @@ -1373,7 +1399,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) MOZ_ASSERT(pn->is()); *answer = true; return true; - + case PNK_OPTCHAIN: MOZ_ASSERT(pn->is()); *answer = true; @@ -1515,8 +1541,10 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR case PNK_CLASSMETHOD: // by PNK_CLASS + case PNK_CLASSFIELD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS - case PNK_CLASSMETHODLIST: // by PNK_CLASS + case PNK_STATICCLASSBLOCK:// by PNK_CLASS + case PNK_CLASSMEMBERLIST: // by PNK_CLASS case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT case PNK_IMPORT_SPEC: // by PNK_IMPORT case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT @@ -1639,11 +1667,11 @@ BytecodeEmitter::tokenStream() bool BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; va_list args; va_start(args, errorNumber); - bool result = tokenStream().reportCompileErrorNumberVA(nullptr, pos.begin, JSREPORT_ERROR, + bool result = tokenStream().reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, errorNumber, args); va_end(args); return result; @@ -1652,7 +1680,7 @@ BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) bool BytecodeEmitter::reportError(const mozilla::Maybe& maybeOffset, unsigned errorNumber, ...) { - uint32_t offset = maybeOffset ? *maybeOffset : tokenStream().currentToken().pos.begin; + uint32_t offset = maybeOffset ? *maybeOffset : *scriptStartOffset; va_list args; va_start(args, errorNumber); @@ -1665,11 +1693,11 @@ BytecodeEmitter::reportError(const mozilla::Maybe& maybeOffset, unsign bool BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...) { - TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos; + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; va_list args; va_start(args, errorNumber); - bool result = tokenStream().reportExtraWarningErrorNumberVA(nullptr, pos.begin, + bool result = tokenStream().reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, args); va_end(args); return result; @@ -2311,12 +2339,18 @@ BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) return false; } + if (!emitInitializeInstanceFields()) { + return false; + } + return true; } bool BytecodeEmitter::emitScript(ParseNode* body) { + setScriptStartOffsetIfUnset(body->pn_pos.begin); + TDZCheckCache tdzCache(this); EmitterScope emitterScope(this); if (sc->isGlobalContext()) { @@ -2335,7 +2369,7 @@ BytecodeEmitter::emitScript(ParseNode* body) return false; } - setFunctionBodyEndPos(body->pn_pos); + setFunctionBodyEndPos(body->pn_pos.end); if (sc->isEvalContext() && !sc->strict() && body->is() && !body->as().isEmptyScope()) @@ -2384,69 +2418,47 @@ BytecodeEmitter::emitScript(ParseNode* body) bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { - ParseNode* body = funNode->body(); + ListNode* paramsBody = &funNode->body()->as(); FunctionBox* funbox = sc->asFunctionBox(); - // The ordering of these EmitterScopes is important. The named lambda - // scope needs to enclose the function scope needs to enclose the extra - // var scope. + MOZ_ASSERT(fieldInitializers_.valid == (funbox->function()->kind() == + JSFunction::FunctionKind::ClassConstructor)); - Maybe namedLambdaEmitterScope; - if (funbox->namedLambdaBindings()) { - namedLambdaEmitterScope.emplace(this); - if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox)) - return false; + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); + + // [stack] + + FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), + Some(paramsBody->pn_pos.end)); + if (!fse.prepareForParameters()) { + // [stack] + return false; } - /* - * Emit a prologue for run-once scripts which will deoptimize JIT code - * if the script ends up running multiple times via foo.caller related - * shenanigans. - * - * Also mark the script so that initializers created within it may be - * given more precise types. - */ - if (isRunOnceLambda()) { - script->setTreatAsRunOnce(); - MOZ_ASSERT(!script->hasRunOnce()); - - switchToPrologue(); - if (!emit1(JSOP_RUNONCE)) - return false; - switchToMain(); + if (!emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; } - setFunctionBodyEndPos(body->pn_pos); - if (!emitTree(body)) + if (!fse.prepareForBody()) { + // [stack] return false; - - if (!updateSourceCoordNotes(body->pn_pos.end)) - return false; - - // Always end the script with a JSOP_RETRVAL. Some other parts of the - // codebase depend on this opcode, - // e.g. InterpreterRegs::setToEndOfScript. - if (!emit1(JSOP_RETRVAL)) - return false; - - if (namedLambdaEmitterScope) { - if (!namedLambdaEmitterScope->leave(this)) - return false; - namedLambdaEmitterScope.reset(); } - if (!JSScript::fullyInitFromEmitter(cx, script, this)) + if (!emitTree(paramsBody->last())) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (!fse.initScript()) return false; - // URL and source map information must be set before firing - // Debugger::onNewScript. Only top-level functions need this, as compiling - // the outer scripts of nested functions already processed the source. - if (emitterMode != LazyFunction && !parent) { - if (!maybeSetDisplayURL() || !maybeSetSourceMap()) - return false; - - tellDebuggerAboutCompiledScript(cx); - } + script->setFieldInitializers(fieldInitializers_); return true; } @@ -2936,67 +2948,90 @@ BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, In bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { - if (!emit1(JSOP_DUP)) // VALUE VALUE + // [stack] VALUE + + DefaultEmitter de(this); + if (!de.prepareForDefault()) { + // [stack] return false; - if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED + } + if (!emitInitializer(defaultExpr, pattern)) { + // [stack] DEFAULTVALUE return false; - if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? - return false; - // Emit source note to enable ion compilation. - if (!newSrcNote(SRC_IF)) - return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) // VALUE - return false; - if (!emit1(JSOP_POP)) // . - return false; - if (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE - return false; - if (!emitJumpTargetAndPatch(jump)) + } + if (!de.emitEnd()) { + // [stack] VALUE/DEFAULTVALUE return false; + } return true; } bool -BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind) +BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name) { if (maybeFun->is()) { // Function doesn't have 'name' property at this point. // Set function's name at compile time. - RootedFunction fun(cx, maybeFun->as().funbox()->function()); + return setFunName(maybeFun->as().funbox()->function(), name); + } - // Single node can be emitted multiple times if it appears in - // array destructuring default. If function already has a name, - // just return. - if (fun->hasCompileTimeName()) { + MOZ_ASSERT(maybeFun->isKind(PNK_CLASS)); + + return emitSetClassConstructorName(name); +} + +bool +BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) +{ + // Single node can be emitted multiple times if it appears in + // array destructuring default. If function already has a name, + // just return. + if (fun->hasCompileTimeName()) { #ifdef DEBUG - RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); - if (!funName) - return false; - MOZ_ASSERT(funName == fun->compileTimeName()); -#endif - return true; - } - - RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + RootedAtom funName(cx, name); if (!funName) return false; - fun->setCompileTimeName(name); + MOZ_ASSERT(funName == fun->compileTimeName()); +#endif return true; } + RootedAtom funName(cx, name); + if (!funName) + return false; + fun->setCompileTimeName(funName); + return true; +} + +bool +BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) +{ uint32_t nameIndex; if (!makeAtomIndex(name, &nameIndex)) return false; if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME return false; - uint8_t kind = uint8_t(prefixKind); + uint8_t kind = uint8_t(FunctionPrefixKind::None); if (!emit2(JSOP_SETFUNNAME, kind)) // FUN return false; return true; } +bool +BytecodeEmitter::emitSetFunctionNameFromStack(uint8_t offset) +{ + // [stack] KEY ... FUN + if (!emitDupAt(offset)) + return false; + // [stack] KEY ... FUN KEY + uint8_t kind = uint8_t(FunctionPrefixKind::None); + if (!emit2(JSOP_SETFUNNAME, kind)) { + // [stack] KEY ... FUN + return false; + } + return true; +} + bool BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) { @@ -3014,13 +3049,6 @@ BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) return true; } -bool -BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) -{ - TDZCheckCache tdzCache(this); - return emitInitializer(initializer, pattern); -} - bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav) { @@ -3703,70 +3731,67 @@ EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset) } bool -BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) +BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, + ParseNode* lhs, ParseNode* rhs) { bool isCompound = compoundOp != JSOP_NOP; + bool isInit = kind == PNK_INITPROP; - // Name assignments are handled separately because choosing ops and when - // to emit BINDNAME is involved and should avoid duplication. - if (lhs->isKind(PNK_NAME)) { - NameOpEmitter noe(this, - lhs->name(), - isCompound - ? NameOpEmitter::Kind::CompoundAssignment - : NameOpEmitter::Kind::SimpleAssignment); - if (!noe.prepareForRhs()) { // ENV? VAL? - return false; - } - - // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on - // the top of the stack and we need to pick the right RHS value. - uint8_t offset = noe.emittedBindOp() ? 2 : 1; - if (!EmitAssignmentRhs(this, rhs, offset)) { // ENV? VAL? RHS - return false; - } - // Assign inferred function name, unless the lhs is parenthesized - if (rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens()) { - MOZ_ASSERT(!isCompound); - RootedAtom name(cx, lhs->name()); - if (!setOrEmitSetFunName(rhs, name)) { // ENV? VAL? RHS - return false; - } - } - - // Emit the compound assignment op if there is one. - if (isCompound) { - if (!emit1(compoundOp)) { // ENV? VAL - return false; - } - } - if (!noe.emitAssignment()) { // VAL - return false; - } - - return true; - } + MOZ_ASSERT_IF(isInit, lhs->isKind(PNK_DOT) || + lhs->isKind(PNK_ELEM)); + Maybe noe; Maybe poe; Maybe eoe; - // Deal with non-name assignments. uint8_t offset = 1; + // Anonymous functions get their inferred name in simple assignments: + // x = function() {}; // x.name === "x" + // In this case, rhs->isDirectRHSAnonFunction() from parsing the statement. + // To suppress this, put the variable in parentheses: + // (x) = function() {}; // x.name === undefined + // In normal property assignments (`obj.x = function(){}`), the anonymous + // function does not have a computed name and rhs->isDirectRHSAnonFunction()==false. + // However, in field initializers (`class C { x = function(){} }`), field + // initialization is implemented via a property or elem assignment *and* + // rhs->isDirectRHSAnonFunction() is set. In this case (detected by `isInit`), + // we'll assign the name of the function using the same plumbing as binding assignments. + // For PNK_NAME and PNK_DOT, the name is compile-time constant, and is stored in `anonFunctionName`. + // For PNK_ELEM, we grab it from the stack before emitting the actual assignment. + RootedAtom anonFunctionName(cx); + bool inferFunctionName = !isCompound && + rhs && rhs->isDirectRHSAnonFunction() && !lhs->isInParens() && + (lhs->isKind(PNK_NAME) || isInit); switch (lhs->getKind()) { + case PNK_NAME: { + noe.emplace(this, + lhs->name(), + isCompound + ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + if (inferFunctionName) { + anonFunctionName = lhs->name(); + } + break; + } case PNK_DOT: { PropertyAccess* prop = &lhs->as(); bool isSuper = prop->isSuper(); poe.emplace(this, isCompound ? PropOpEmitter::Kind::CompoundAssignment - : PropOpEmitter::Kind::SimpleAssignment, + : isInit ? PropOpEmitter::Kind::PropInit + : PropOpEmitter::Kind::SimpleAssignment, isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); if (!poe->prepareForObj()) { return false; } + if (inferFunctionName) { + anonFunctionName = &prop->name(); + } if (isSuper) { UnaryNode* base = &prop->expression().as(); if (!emitGetThisForSuperBase(base)) { // THIS SUPERBASE @@ -3787,7 +3812,8 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) eoe.emplace(this, isCompound ? ElemOpEmitter::Kind::CompoundAssignment - : ElemOpEmitter::Kind::SimpleAssignment, + : isInit ? ElemOpEmitter::Kind::PropInit + : ElemOpEmitter::Kind::SimpleAssignment, isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); @@ -3856,6 +3882,15 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) } switch (lhs->getKind()) { + case PNK_NAME: + if (!noe->prepareForRhs()) { // ENV? VAL? + return false; + } + // If we emitted a BIND[G]NAME, then the scope is on + // the top of the stack and we need to pick the right RHS value. + if (noe->emittedBindOp()) + offset += 1; + break; case PNK_DOT: if (!poe->prepareForRhs()) { // [Simple,Super] // // THIS SUPERBASE @@ -3887,6 +3922,26 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) if (!EmitAssignmentRhs(this, rhs, offset)) // ... VAL? RHS return false; + // Assign inferred function name + if (inferFunctionName) { + MOZ_ASSERT(!isCompound); + if (anonFunctionName) { + // Name is an atom known at compile time + MOZ_ASSERT_IF(!lhs->isKind(PNK_NAME), isInit); + if (!setOrEmitSetFunName(rhs, anonFunctionName)) { // ENV? VAL? RHS + return false; + } + } else if (lhs->isKind(PNK_ELEM)) { + // offset points to the SP relative to RHS. // [Simple,Super] offset = 4 + // Find KEY relative to that. // {offset} THIS KEY SUPERBASE FUN + // // [Simple,Other] offset = 3 + // // {offset} OBJ KEY FUN + MOZ_ASSERT(offset >= 2); + if (!emitSetFunctionNameFromStack(offset - 2)) + return false; + } + } + /* If += etc., emit the binary operator with a source note. */ if (isCompound) { if (!newSrcNote(SRC_ASSIGNOP)) @@ -3897,6 +3952,14 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) /* Finally, emit the specialized assignment bytecode. */ switch (lhs->getKind()) { + case PNK_NAME: { + if (!noe->emitAssignment()) { // VAL + return false; + } + + noe.reset(); + break; + } case PNK_DOT: { PropertyAccess* prop = &lhs->as(); if (!poe->emitAssignment(prop->key().atom())) { // VAL @@ -3928,6 +3991,225 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs) return true; } +bool +BytecodeEmitter::emitShortCircuitAssignment(ParseNodeKind kind, JSOp op, + ParseNode* lhs, ParseNode* rhs) +{ + TDZCheckCache tdzCache(this); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + RootedAtom name(cx); + + // Select the appropriate emitter based on the left-hand side. + Maybe noe; + Maybe poe; + Maybe eoe; + + int32_t startDepth = stackDepth; + + // Number of values pushed onto the stack in addition to the lhs value. + int32_t numPushed; + + // Evaluate the left-hand side expression and compute any stack values needed + // for the assignment. + switch (lhs->getKind()) { + case PNK_NAME: { + name = lhs->as().name(); + noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment); + + if (!noe->prepareForRhs()) { + // [stack] ENV? LHS + return false; + } + + numPushed = noe->emittedBindOp(); + break; + } + + case PNK_DOT: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + + poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + if (!poe->prepareForObj()) + return false; + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + if (!poe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + numPushed = 1 + isSuper; + break; + } + + case PNK_ELEM: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + if (!eoe->emitGet()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + if (!eoe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + numPushed = 2 + isSuper; + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(stackDepth == startDepth + numPushed + 1); + + // Test for the short-circuit condition. + JumpList jump; + if (!emitJump(op, &jump)) { + // [stack] ... LHS + return false; + } + + // The short-circuit condition wasn't fulfilled, pop the left-hand side value + // which was kept on the stack. + if (!emit1(JSOP_POP)) { + // [stack] ... + return false; + } + + // TODO: Open spec issue about setting inferred function names. + // + if (!emitTree(rhs)) { + // [stack] ... RHS + return false; + } + + // Perform the actual assignment. + switch (lhs->getKind()) { + case PNK_NAME: { + if (!noe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case PNK_DOT: { + PropertyAccess* prop = &lhs->as(); + + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] RHS + return false; + } + break; + } + + case PNK_ELEM: { + if (!eoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(stackDepth == startDepth + 1); + + // Join with the short-circuit jump and pop anything left on the stack. + if (numPushed > 0) { + JumpList jumpAroundPop; + if (!emitJump(JSOP_GOTO, &jumpAroundPop)) { + // [stack] RHS + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + // [stack] ... LHS + return false; + } + + // Reconstruct the stack depth after the jump. + stackDepth = startDepth + 1 + numPushed; + + // Move the left-hand side value to the bottom and pop the rest. + if (!emitUnpickN(numPushed)) { + // [stack] LHS ... + return false; + } + + if (!emitPopN(numPushed)) { + // [stack] LHS + return false; + } + + if (!emitJumpTargetAndPatch(jumpAroundPop)) { + // [stack] LHS | RHS + return false; + } + } else { + if (!emitJumpTargetAndPatch(jump)) { + // [stack] LHS | RHS + return false; + } + } + + MOZ_ASSERT(stackDepth == startDepth + 1); + + return true; +} + bool ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects, MutableHandleValue vp, Value* compare, size_t ncompare, @@ -4156,7 +4438,7 @@ BytecodeEmitter::emitCatch(TernaryNode* catchNode) break; case PNK_NAME: - if (!emitLexicalInitialization(pn2)) + if (!emitLexicalInitialization(&pn2->as())) return false; if (!emit1(JSOP_POP)) return false; @@ -4380,11 +4662,21 @@ BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLi MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) { - TDZCheckCache tdzCache(this); + LexicalScopeEmitter lse(this); ParseNode* body = lexicalScope->scopeBody(); - if (lexicalScope->isEmptyScope()) - return emitLexicalScopeBody(body); + if (lexicalScope->isEmptyScope()) { + if (!lse.emitEmptyScope()) + return false; + + if (!emitLexicalScopeBody(body)) + return false; + + if (!lse.emitEnd()) + return false; + + return true; + } // Update line number notes before emitting TDZ poison in // EmitterScope::enterLexical to avoid spurious pausing on seemingly @@ -4410,7 +4702,6 @@ BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) return false; } - EmitterScope emitterScope(this); ScopeKind kind; if (body->isKind(PNK_CATCH)) { TernaryNode* catchNode = &body->as(); @@ -4420,21 +4711,21 @@ BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope) } else kind = ScopeKind::Lexical; - if (!emitterScope.enterLexical(this, kind, lexicalScope->scopeBindings())) + if (!lse.emitScope(kind, lexicalScope->scopeBindings())) return false; if (body->isKind(PNK_FOR)) { // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are // lexical declarations in the head. Signal this by passing a // non-nullptr lexical scope. - if (!emitFor(&body->as(), &emitterScope)) + if (!emitFor(&body->as(), &lse.emitterScope())) return false; } else { if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) return false; } - return emitterScope.leave(this); + return lse.emitEnd(); } bool @@ -4711,7 +5002,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) // initialization is just assigning the iteration value to a target // expression. if (!parser->handler.isDeclarationList(target)) - return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL + return emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, target, nullptr); // ... ITERVAL // Otherwise, per-loop initialization is (possibly) declaration // initialization. If the declaration is a lexical declaration, it must be @@ -4725,8 +5016,19 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) MOZ_ASSERT(target->isForLoopDeclaration()); target = parser->handler.singleBindingFromDeclaration(&target->as()); + NameNode* nameNode = nullptr; if (target->isKind(PNK_NAME)) { - NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize); + nameNode = &target->as(); + } else if (target->isKind(PNK_ASSIGN) || + target->isKind(PNK_INITPROP)) { + BinaryNode* assignNode = &target->as(); + if (assignNode->left()->is()) { + nameNode = &assignNode->left()->as(); + } + } + + if (nameNode) { + NameOpEmitter noe(this, nameNode->name(), NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } @@ -4752,7 +5054,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) return true; } - MOZ_ASSERT(!target->isKind(PNK_ASSIGN), + MOZ_ASSERT(!target->isKind(PNK_ASSIGN) && !target->isKind(PNK_INITPROP), "for-in/of loop destructuring declarations can't have initializers"); MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)); @@ -4760,7 +5062,7 @@ BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) } bool -BytecodeEmitter::emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); @@ -4933,7 +5235,7 @@ BytecodeEmitter::emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterSco } bool -BytecodeEmitter::emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); MOZ_ASSERT(forNode->isOp(JSOP_ITER)); @@ -5082,7 +5384,7 @@ BytecodeEmitter::emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterSco /* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { LoopControl loopInfo(this, StatementKind::ForLoop); @@ -5268,7 +5570,7 @@ BytecodeEmitter::emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitte } bool -BytecodeEmitter::emitFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope) +BytecodeEmitter::emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { MOZ_ASSERT(forNode->isKind(PNK_FOR)); @@ -5393,7 +5695,7 @@ BytecodeEmitter::emitComprehensionForOf(ForNode* forNode) // Notice: Comprehension for-of doesn't perform IteratorClose, since it's // not in the spec. - if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE + if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, loopVariableName, nullptr)) // ITER RESULT VALUE return false; // Remove VALUE from the stack to release it. @@ -5530,7 +5832,7 @@ BytecodeEmitter::emitComprehensionForIn(ForNode* forNode) // Emit code to assign the enumeration value to the left hand side, but // also leave it on the stack. - if (!emitAssignment(forHead->kid2(), JSOP_NOP, nullptr)) + if (!emitAssignmentOrInit(PNK_ASSIGN, JSOP_NOP, forHead->kid2(), nullptr)) return false; /* The stack should be balanced around the assignment opcode sequence. */ @@ -5598,12 +5900,18 @@ BytecodeEmitter::emitComprehensionFor(ForNode* forNode) } MOZ_NEVER_INLINE bool -BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) +BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto /* = false */, + ListNode* classContentsIfConstructor /* = nullptr */) { FunctionBox* funbox = funNode->funbox(); RootedFunction fun(cx, funbox->function()); - RootedAtom name(cx, fun->explicitName()); - MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); + + MOZ_ASSERT((classContentsIfConstructor != nullptr) == (funbox->function()->kind() == + JSFunction::FunctionKind::ClassConstructor)); + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted()); /* * Set the |wasEmitted| flag in the funbox once the function has been @@ -5611,43 +5919,9 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) * function will be seen by emitFunction in two places. */ if (funbox->wasEmitted) { - // Annex B block-scoped functions are hoisted like any other - // block-scoped function to the top of their scope. When their - // definitions are seen for the second time, we need to emit the - // assignment that assigns the function to the outer 'var' binding. - if (funbox->isAnnexB) { - // Get the location of the 'var' binding in the body scope. The - // name must be found, else there is a bug in the Annex B handling - // in Parser. - // - // In sloppy eval contexts, this location is dynamic. - Maybe lhsLoc = locationOfNameBoundInScope(name, varEmitterScope); - - // If there are parameter expressions, the var name could be a - // parameter. - if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope()) - lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame()); - - if (!lhsLoc) { - lhsLoc = Some(NameLocation::DynamicAnnexBVar()); - } else { - MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || - lhsLoc->bindingKind() == BindingKind::FormalParameter || - (lhsLoc->bindingKind() == BindingKind::Let && - sc->asFunctionBox()->hasParameterExprs)); - } - - NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); - if (!noe.prepareForRhs()) { - return false; - } - if (!emitGetName(name)) { - return false; - } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) - return false; + if (!fe.emitAgain()) { + // [stack] + return false; } MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); @@ -5655,172 +5929,73 @@ BytecodeEmitter::emitFunction(FunctionNode* funNode, bool needsProto) return true; } - funbox->wasEmitted = true; - /* - * Mark as singletons any function which will only be executed once, or - * which is inner to a lambda we only expect to run once. In the latter - * case, if the lambda runs multiple times then CloneFunctionObject will - * make a deep clone of its contents. - */ if (fun->isInterpreted()) { - bool singleton = checkRunOnceContext(); - if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) - return false; - - SharedContext* outersc = sc; if (fun->isInterpretedLazy()) { - // We need to update the static scope chain regardless of whether - // the LazyScript has already been initialized, due to the case - // where we previously successfully compiled an inner function's - // lazy script but failed to compile the outer script after the - // fact. If we attempt to compile the outer script again, the - // static scope chain will be newly allocated and will mismatch - // the previously compiled LazyScript's. - ScriptSourceObject* source = &script->sourceObject()->as(); - fun->lazyScript()->setEnclosingScopeAndSource(innermostScope(), source); - if (emittingRunOnceLambda) - fun->lazyScript()->setTreatAsRunOnce(); - } else { - MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript); - - // Inherit most things (principals, version, etc) from the - // parent. Use default values for the rest. - Rooted parent(cx, script); - MOZ_ASSERT(parent->getVersion() == parser->options().version); - MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); - const TransitiveCompileOptions& transitiveOptions = parser->options(); - CompileOptions options(cx, transitiveOptions); - - Rooted sourceObject(cx, script->sourceObject()); - Rooted script(cx, JSScript::Create(cx, options, sourceObject, - funbox->bufStart, funbox->bufEnd, - funbox->toStringStart, - funbox->toStringEnd)); - if (!script) + if (!fe.emitLazy()) { + // [stack] FUN? return false; - - BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, - funNode->pn_pos, emitterMode); - if (!bce2.init()) - return false; - - /* We measured the max scope depth when we parsed the function. */ - if (!bce2.emitFunctionScript(funNode)) - return false; - - if (funbox->isLikelyConstructorWrapper()) - script->setLikelyConstructorWrapper(); - } - - if (outersc->isFunctionBox()) - outersc->asFunctionBox()->setHasInnerFunctions(); - } else { - MOZ_ASSERT(IsAsmJSModule(fun)); - } - - /* Make the function object a literal in the outer script's pool. */ - unsigned index = objectList.add(funNode->funbox()); - - /* Non-hoisted functions simply emit their respective op. */ - if (!funNode->functionIsHoisted()) { - /* JSOP_LAMBDA_ARROW is always preceded by a new.target */ - MOZ_ASSERT(fun->isArrow() == (funNode->syntaxKind() == FunctionSyntaxKind::Arrow)); - if (funbox->isAsync()) { - MOZ_ASSERT(!needsProto); - return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow(), - fun->isStarGenerator()); - } - - if (fun->isArrow()) { - if (sc->allowNewTarget()) { - if (!emit1(JSOP_NEWTARGET)) - return false; - } else { - if (!emit1(JSOP_NULL)) - return false; } - } - if (needsProto) { - MOZ_ASSERT(funNode->syntaxKind() == FunctionSyntaxKind::DerivedClassConstructor); - return emitIndex32(JSOP_FUNWITHPROTO, index); - } - - // This is a FunctionExpression, ArrowFunctionExpression, or class - // constructor. Emit the single instruction (without location info). - JSOp op = funNode->syntaxKind() == FunctionSyntaxKind::Arrow - ? JSOP_LAMBDA_ARROW - : JSOP_LAMBDA; - return emitIndex32(op, index); - } - - MOZ_ASSERT(!needsProto); - - bool topLevelFunction; - if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) { - // No nested functions inside other functions are top-level. - topLevelFunction = false; - } else { - // In sloppy eval scripts, top-level functions in are accessed - // dynamically. In global and module scripts, top-level functions are - // those bound in the var scope. - NameLocation loc = lookupName(name); - topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || - loc.bindingKind() == BindingKind::Var; - } - - if (topLevelFunction) { - if (sc->isModuleContext()) { - // For modules, we record the function and instantiate the binding - // during ModuleInstantiate(), before the script is run. - - RootedModuleObject module(cx, sc->asModuleContext()->module()); - if (!module->noteFunctionDeclaration(cx, name, fun)) - return false; - } else { - MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext()); - MOZ_ASSERT(funNode->syntaxKind() == FunctionSyntaxKind::Statement); - switchToPrologue(); - if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(), - fun->isStarGenerator())) - { - return false; - } - } else { - if (!emitIndex32(JSOP_LAMBDA, index)) - return false; + if (classContentsIfConstructor) { + fun->lazyScript()->setFieldInitializers(setupFieldInitializers(classContentsIfConstructor, + FieldPlacement::Instance)); } - if (!emit1(JSOP_DEFFUN)) - return false; - if (!updateSourceCoordNotes(funNode->pn_pos.begin)) - return false; - switchToMain(); + return true; } - } else { - // For functions nested within functions and blocks, make a lambda and - // initialize the binding name of the function in the current scope. - NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { + if (!fe.prepareForNonLazy()) { + // [stack] return false; } - if (funbox->isAsync()) { - if (!emitAsyncWrapper(index, /* needsHomeObject = */ false, - /* isArrow = */ false, funbox->isStarGenerator())) - { - return false; - } - } else { - if (!emitIndexOp(JSOP_LAMBDA, index)) { - return false; - } + + // Inherit most things (principals, version, etc) from the + // parent. Use default values for the rest. + Rooted parent(cx, script); + MOZ_ASSERT(parent->getVersion() == parser->options().version); + MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors()); + const TransitiveCompileOptions& transitiveOptions = parser->options(); + CompileOptions options(cx, transitiveOptions); + + Rooted sourceObject(cx, script->sourceObject()); + Rooted script(cx, JSScript::Create(cx, options, sourceObject, + funbox->bufStart, funbox->bufEnd, + funbox->toStringStart, + funbox->toStringEnd)); + if (!script) + return false; + + FieldInitializers fieldInitializers = FieldInitializers::Invalid(); + if (classContentsIfConstructor) { + fieldInitializers = setupFieldInitializers(classContentsIfConstructor, FieldPlacement::Instance); } - if (!noe.emitAssignment()) + + BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr, + funNode->pn_pos, emitterMode, fieldInitializers); + if (!bce2.init()) return false; - if (!emit1(JSOP_POP)) + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(funNode)) return false; + + // fieldInitializers are copied to the JSScript inside BytecodeEmitter + + if (funbox->isLikelyConstructorWrapper()) { + script->setLikelyConstructorWrapper(); + } + + if (!fe.emitNonLazyEnd()) { + // [stack] FUN? + return false; + } + + return true; + } + + if (!fe.emitAsmJSModule()) { + // [stack] + return false; } return true; @@ -6171,8 +6346,7 @@ BytecodeEmitter::emitReturn(UnaryNode* returnNode) // We know functionBodyEndPos is set because "return" is only // valid in a function, and so we've passed through // emitFunctionScript. - MOZ_ASSERT(functionBodyEndPosSet); - if (!updateSourceCoordNotes(functionBodyEndPos)) + if (!updateSourceCoordNotes(*functionBodyEndPos)) return false; /* @@ -7085,7 +7259,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn) FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); - if (!funbox->hasRest()) + if (!funbox->hasRest()) return false; if (!pn->isKind(PNK_NAME)) { @@ -7715,168 +7889,642 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional, } bool -BytecodeEmitter::emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, PropListType type) +BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type) { + // [stack] CTOR? OBJ + + size_t curFieldKeyIndex = 0; + size_t curStaticFieldKeyIndex = 0; for (ParseNode* propdef : obj->contents()) { - if (!updateSourceCoordNotes(propdef->pn_pos.begin)) - return false; + if (propdef->is()) { + MOZ_ASSERT(type == ClassBody); + // Only handle computing field keys here: the .initializers lambda array + // is created elsewhere. + ClassField* field = &propdef->as(); + if (field->name().getKind() == PNK_COMPUTED_NAME) { + HandlePropertyName fieldKeys = field->isStatic() ? cx->names().dotStaticFieldKeys + : cx->names().dotFieldKeys; + if (!emitGetName(fieldKeys)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + ParseNode* nameExpr = field->name().as().kid(); + + if (!emitTree(nameExpr)) { + // [stack] ARRAY KEY + return false; + } + + if (!emit1(JSOP_TOID)) { + // [stack] ARRAY KEY + return false; + } + + size_t fieldKeysIndex = field->isStatic() ? curStaticFieldKeyIndex++ + : curFieldKeyIndex++; + if (!emitUint32Operand(JSOP_INITELEM_ARRAY, fieldKeysIndex)) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] CTOR? OBJ + return false; + } + } + continue; + } + + if (propdef->is()) { + // Static class blocks are emitted as part of + // emitCreateFieldInitializers. + continue; + } + + if (propdef->is()) { + // Constructors are sometimes wrapped in LexicalScopeNodes. As we already + // handled emitting the constructor, skip it. + MOZ_ASSERT(propdef->as().scopeBody()->isKind(PNK_CLASSMETHOD)); + continue; + } // Handle __proto__: v specially because *only* this form, and no other // involving "__proto__", performs [[Prototype]] mutation. if (propdef->isKind(PNK_MUTATEPROTO)) { + // [stack] OBJ MOZ_ASSERT(type == ObjectLiteral); - if (!emitTree(propdef->as().kid())) + if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) { + // [stack] OBJ return false; - objp.set(nullptr); - if (!emit1(JSOP_MUTATEPROTO)) + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ + return false; + } continue; } if (propdef->isKind(PNK_SPREAD)) { MOZ_ASSERT(type == ObjectLiteral); - - if (!emit1(JSOP_DUP)) + // [stack] OBJ + if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ return false; - - if (!emitTree(propdef->as().kid())) + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL return false; - - if (!emitCopyDataProperties(CopyOption::Unfiltered)) + } + if (!pe.emitSpread()) { + // [stack] OBJ return false; - - objp.set(nullptr); + } continue; } - bool extraPop = false; - if (type == ClassBody && propdef->as().isStatic()) { - extraPop = true; - if (!emit1(JSOP_DUP2)) - return false; - if (!emit1(JSOP_POP)) - return false; - } - /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ - ParseNode* key = propdef->as().left(); - bool isIndex = false; - if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->as().value())) - return false; - isIndex = true; - } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { - // EmitClass took care of constructor already. - if (type == ClassBody && key->as().atom() == cx->names().constructor && - !propdef->as().isStatic()) - { - continue; - } - } else { - MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME)); - if (!emitComputedPropertyName(&key->as())) - return false; - isIndex = true; - } - - /* Emit code for the property initializer. */ - ParseNode* propVal = propdef->as().right(); - if (!emitTree(propVal)) - return false; + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + ParseNode* propVal = prop->right(); + bool isPropertyAnonFunctionOrClass = propVal->isDirectRHSAnonFunction(); JSOp op = propdef->getOp(); - MOZ_ASSERT(op == JSOP_INITPROP || - op == JSOP_INITPROP_GETTER || + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); - FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get - : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set - : FunctionPrefixKind::None; + auto emitValue = [this, &propVal, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? - if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) - objp.set(nullptr); - - if (propVal->is() && - propVal->as().funbox()->needsHomeObject()) { - FunctionBox* funbox = propVal->as().funbox(); - MOZ_ASSERT(funbox->function()->allowSuperProperty()); - bool isAsync = funbox->isAsync(); - if (isAsync) { - if (!emit1(JSOP_SWAP)) - return false; - } - if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync)) + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL return false; - if (isAsync) { - if (!emit1(JSOP_POP)) - return false; } - } - // Class methods are not enumerable. - if (type == ClassBody) { - switch (op) { - case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - } + if (propVal->is() && + propVal->as().funbox()->needsHomeObject()) { + FunctionBox* funbox = propVal->as().funbox(); + MOZ_ASSERT(funbox->function()->allowSuperProperty()); - if (isIndex) { - objp.set(nullptr); - switch (op) { - case JSOP_INITPROP: op = JSOP_INITELEM; break; - case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; - case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; - case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; - default: MOZ_CRASH("Invalid op"); - } - if (propVal->isDirectRHSAnonFunction()) { - if (!emitDupAt(1)) - return false; - if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) - return false; - } - if (!emit1(op)) - return false; - } else { - MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)); - - uint32_t index; - if (!makeAtomIndex(key->as().atom(), &index)) - return false; - - if (objp) { - MOZ_ASSERT(type == ObjectLiteral); - MOZ_ASSERT(!IsHiddenInitOp(op)); - MOZ_ASSERT(!objp->inDictionaryMode()); - Rooted id(cx, AtomToId(key->as().atom())); - if (!NativeDefineProperty(cx, objp, id, UndefinedHandleValue, nullptr, nullptr, - JSPROP_ENUMERATE)) - { + if (!pe.emitInitHomeObject(funbox->asyncKind())) { + // [stack] CTOR? OBJ CTOR? KEY? FUN return false; } - if (objp->inDictionaryMode()) - objp.set(nullptr); + } + return true; + }; + + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + + if (key->isKind(PNK_NUMBER)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; } - if (propVal->isDirectRHSAnonFunction()) { - RootedAtom keyName(cx, key->as().atom()); - if (!setOrEmitSetFunName(propVal, keyName, prefixKind)) + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitIndexSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); } - if (!emitIndex32(op, index)) - return false; + continue; } - if (extraPop) { - if (!emit1(JSOP_POP)) + if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { + // EmitClass took care of constructor already. + if (type == ClassBody && key->as().atom() == cx->names().constructor && + !propdef->as().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + RootedFunction anonFunction(cx); + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP); + + if (propVal->is()) { + // When the value is function, we set the function's name + // at the compile-time, instead of emitting SETFUNNAME. + FunctionBox* funbox = propVal->as().funbox(); + anonFunction = funbox->function(); + } else { + // Only object literal can have a property where key is + // name and value is an anonymous class. + // + // ({ foo: class {} }); + MOZ_ASSERT(type == ObjectLiteral); + MOZ_ASSERT(propVal->isKind(PNK_CLASS)); + } + } + + RootedAtom keyAtom(cx, key->as().atom()); + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass, + anonFunction)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitGetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(!isPropertyAnonFunctionOrClass); + if (!pe.emitInitSetter(keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + break; + default: MOZ_CRASH("Invalid op"); + } + + continue; + } + + MOZ_ASSERT(key->isKind(PNK_COMPUTED_NAME)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitTree(key->as().kid())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + switch (op) { + case JSOP_INITPROP: + if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_GETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedGetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + case JSOP_INITPROP_SETTER: + MOZ_ASSERT(isPropertyAnonFunctionOrClass); + if (!pe.emitInitComputedSetter()) { + // [stack] CTOR? OBJ + return false; + } + break; + default: + MOZ_CRASH("Invalid op"); } } + + return true; +} + +static bool +HasInitializer(ParseNode* node, bool isStaticContext) +{ + // For the purposes of bytecode emission, StaticClassBlocks are treated as if + // they were static initializers. + return (node->is() && + node->as().isStatic() == isStaticContext) || + (isStaticContext && node->is()); +} + +static FunctionNode* +GetInitializer(ParseNode* node, bool isStaticContext) +{ + MOZ_ASSERT(HasInitializer(node, isStaticContext)); + MOZ_ASSERT_IF(!node->is(), isStaticContext); + return node->is() ? node->as().initializer() + : node->as().function(); +} + + +FieldInitializers +BytecodeEmitter::setupFieldInitializers(ListNode* classMembers, FieldPlacement placement) +{ + bool isStatic = placement == FieldPlacement::Static; + size_t numFields = classMembers->count_if([isStatic](ParseNode* propdef) { + return HasInitializer(propdef, isStatic); + }); + + return FieldInitializers(numFields); +} + +// Purpose of .fieldKeys: +// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, not +// object construction time. The transformation to do so is roughly as follows: +// +// class C { +// [keyExpr] = valueExpr; +// } +// --> +// let .fieldKeys = [keyExpr]; +// let .initializers = [ +// () => { +// this[.fieldKeys[0]] = valueExpr; +// } +// ]; +// class C { +// constructor() { +// .initializers[0](); +// } +// } +// +// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];` +// BytecodeEmitter::emitPropertyList fills in the elements of the array. +// See Parser::fieldInitializer for the `this[.fieldKeys[0]]` part. +bool +BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, FieldPlacement placement) +{ + bool isStatic = placement == FieldPlacement::Static; + size_t numFieldKeys = obj->count_if([isStatic](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() == isStatic && + propdef->as().name().getKind() == PNK_COMPUTED_NAME; + }); + + if (numFieldKeys == 0) + return true; + + HandlePropertyName fieldKeys = isStatic ? cx->names().dotStaticFieldKeys + : cx->names().dotFieldKeys; + NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) + return false; + + if (!emitUint32Operand(JSOP_NEWARRAY, numFieldKeys)) { + // [stack] ARRAY + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, + FieldPlacement placement) +{ + // FieldPlacement::Instance + // [stack] HOMEOBJ HERITAGE? + // + // FieldPlacement::Static + // [stack] CTOR HOMEOBJ + FieldInitializers fieldInitializers = setupFieldInitializers(obj, placement); + MOZ_ASSERT(fieldInitializers.valid); + size_t numFields = fieldInitializers.numFieldInitializers; + + if (numFields == 0) + return true; + + bool isStatic = placement == FieldPlacement::Static; + if (!ce.prepareForFieldInitializers(numFields, isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + for (ParseNode* propdef : obj->contents()) { + if (!HasInitializer(propdef, isStatic)) + continue; + + FunctionNode* initializer = GetInitializer(propdef, isStatic); + if (!ce.prepareForFieldInitializer()) + return false; + if (!emitTree(initializer)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA + return false; + } + if (initializer->funbox()->needsHomeObject()) { + MOZ_ASSERT(initializer->funbox()->function()->allowSuperProperty()); + if (!ce.emitFieldInitializerHomeObject(isStatic)) { + // [stack] CTOR OBJ ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA + return false; + } + } + if (!ce.emitStoreFieldInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + } + + if (!ce.emitFieldInitializersEnd()) { + // [stack] HOMEOBJ HERITAGE? + // or: + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +const FieldInitializers& +BytecodeEmitter::findFieldInitializersForCall() +{ + for (BytecodeEmitter* current = this; current; current = current->parent) { + if (current->sc->isFunctionBox()) { + FunctionBox* box = current->sc->asFunctionBox(); + if (box->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { + const FieldInitializers& fieldInitializers = current->getFieldInitializers(); + MOZ_ASSERT(fieldInitializers.valid); + return fieldInitializers; + } + } + } + + for (ScopeIter si(innermostScope()); si; si++) { + if (si.scope()->is()) { + JSFunction* fun = si.scope()->as().canonicalFunction(); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + const FieldInitializers& fieldInitializers = fun->isInterpretedLazy() + ? fun->lazyScript()->getFieldInitializers() + : fun->nonLazyScript()->getFieldInitializers(); + MOZ_ASSERT(fieldInitializers.valid); + return fieldInitializers; + } + } + } + + MOZ_CRASH("Constructor for field initializers not found."); +} + +bool +BytecodeEmitter::emitInitializeInstanceFields() +{ + const FieldInitializers& fieldInitializers = findFieldInitializersForCall(); + size_t numFields = fieldInitializers.numFieldInitializers; + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->names().dotInitializers)) { + // [stack] ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + if (fieldIndex < numFields - 1) { + // We DUP to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra POP at the end of the loop. + if (!emit1(JSOP_DUP)) { + // [stack] ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] ARRAY? ARRAY INDEX + return false; + } + + // Don't use CALLELEM here, because the receiver of the call != the receiver + // of this getelem. (Specifically, the call receiver is `this`, and the + // receiver of this getelem is `.initializers`) + if (!emit1(JSOP_GETELEM)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(cx->names().dotThis)) { + // [stack] ARRAY? FUNC THIS + return false; + } + + if (!emitCall(JSOP_CALL_IGNORES_RV, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] ARRAY? + return false; + } + } + + return true; +} + +bool +BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) +{ + size_t numFields = classMembers->count_if([](ParseNode* propdef) { + return HasInitializer(propdef, true); + }); + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->names().dotStaticInitializers)) { + // [stack] CTOR ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + bool hasNext = fieldIndex < numFields - 1; + if (fieldIndex < numFields - 1) { + // We DUP to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra POP at the end of the loop. + if (!emit1(JSOP_DUP)) { + // [stack] CTOR ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] CTOR ARRAY? ARRAY INDEX + return false; + } + + // Don't use CALLELEM here, because the receiver of the call != the receiver + // of this getelem. (Specifically, the call receiver is `ctor`, and the + // receiver of this getelem is `.staticInitializers`) + if (!emit1(JSOP_GETELEM)) { + // [stack] CTOR ARRAY? FUNC + return false; + } + + if (!emitDupAt(1 + hasNext)) { + // [stack] CTOR ARRAY? FUNC CTOR + return false; + } + + if (!emitCall(JSOP_CALL_IGNORES_RV, 0)) { + // [stack] CTOR ARRAY? RVAL + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] CTOR ARRAY? + return false; + } + } + + // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to + // avoid keeping the arrays alive indefinitely. + auto clearStaticFieldSlot = [&](HandlePropertyName name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + + if (!emit1(JSOP_UNDEFINED)) { + // [stack] ENV? VAL? UNDEFINED + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; + }; + + if (!clearStaticFieldSlot(cx->names().dotStaticInitializers)) + return false; + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() && + propdef->as().name().getKind() == PNK_COMPUTED_NAME; + }; + + if (classMembers->any_of(isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot(cx->names().dotStaticFieldKeys)) + return false; + } + return true; } @@ -7888,38 +8536,21 @@ BytecodeEmitter::emitObject(ListNode* objNode) if (!objNode->hasNonConstInitializer() && objNode->head() && checkSingletonContext()) return emitSingletonInitialiser(objNode); - /* - * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing - * a new object and defining (in source order) each property on the object - * (or mutating the object's [[Prototype]], in the case of __proto__). - */ - ptrdiff_t offset = this->offset(); - if (!emitNewInit(JSProto_Object)) + // [stack] + + ObjectEmitter oe(this); + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ + return false; + } + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + if (!oe.emitEnd()) { + // [stack] OBJ return false; - - // Try to construct the shape of the object as we go, so we can emit a - // JSOP_NEWOBJECT with the final shape instead. - // In the case of computed property names and indices, we cannot fix the - // shape at bytecode compile time. When the shape cannot be determined, - // |obj| is nulled out. - - // No need to do any guessing for the object kind, since we know the upper - // bound of how many properties we plan to have. - gc::AllocKind kind = gc::GetGCObjectKind(objNode->count()); - RootedPlainObject obj(cx, NewBuiltinClassInstance(cx, kind, TenuredObject)); - if (!obj) - return false; - - if (!emitPropertyList(objNode, &obj, ObjectLiteral)) - return false; - - if (obj) { - // The object survived and has a predictable shape: update the original - // bytecode. - if (!replaceNewInitWithNewObject(obj, offset)) - return false; } - return true; } @@ -8133,128 +8764,24 @@ BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) return emit1(op); } -bool -BytecodeEmitter::emitFunctionFormalParametersAndBody(ListNode* paramsBody) -{ - MOZ_ASSERT(paramsBody->isKind(PNK_PARAMSBODY)); - - ParseNode* funBody = paramsBody->last(); - FunctionBox* funbox = sc->asFunctionBox(); - - TDZCheckCache tdzCache(this); - - if (funbox->hasParameterExprs) { - EmitterScope funEmitterScope(this); - if (!funEmitterScope.enterFunction(this, funbox)) - return false; - - if (!emitInitializeFunctionSpecialNames()) - return false; - - if (!emitFunctionFormalParameters(paramsBody)) - return false; - - { - Maybe extraVarEmitterScope; - - if (funbox->hasExtraBodyVarScope()) { - extraVarEmitterScope.emplace(this); - if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox)) - return false; - - // After emitting expressions for all parameters, copy over any - // formal parameters which have been redeclared as vars. For - // example, in the following, the var y in the body scope is 42: - // - // function f(x, y = 42) { var y; } - // - RootedAtom name(cx); - if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) { - for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) { - name = bi.name(); - - // There may not be a var binding of the same name. - if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr())) - continue; - - // The '.this' and '.generator' function special - // bindings should never appear in the extra var - // scope. 'arguments', however, may. - MOZ_ASSERT(name != cx->names().dotThis && - name != cx->names().dotGenerator); - - NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - return false; - } - - NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope); - if (!emitGetNameAtLocation(name, paramLoc)) { - return false; - } - if (!noe.emitAssignment()) - return false; - if (!emit1(JSOP_POP)) - return false; - } - } - } - - if (!emitFunctionBody(funBody)) - return false; - - if (extraVarEmitterScope && !extraVarEmitterScope->leave(this)) - return false; - } - - return funEmitterScope.leave(this); - } - - // No parameter expressions. Enter the function body scope and emit - // everything. - // - // One caveat is that Debugger considers ops in the prologue to be - // unreachable (i.e. cannot set a breakpoint on it). If there are no - // parameter exprs, any unobservable environment ops (like pushing the - // call object, setting '.this', etc) need to go in the prologue, else it - // messes up breakpoint tests. - EmitterScope emitterScope(this); - - switchToPrologue(); - if (!emitterScope.enterFunction(this, funbox)) - return false; - - if (!emitInitializeFunctionSpecialNames()) - return false; - switchToMain(); - - if (!emitFunctionFormalParameters(paramsBody)) - return false; - - if (!emitFunctionBody(funBody)) - return false; - - return emitterScope.leave(this); -} - bool BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) { ParseNode* funBody = paramsBody->last(); FunctionBox* funbox = sc->asFunctionBox(); - EmitterScope* funScope = innermostEmitterScope(); - - bool hasParameterExprs = funbox->hasParameterExprs; bool hasRest = funbox->hasRest(); - uint16_t argSlot = 0; - for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next, argSlot++) { + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg = paramsBody->head(); arg != funBody; arg = arg->pn_next) { ParseNode* bindingElement = arg; ParseNode* initializer = nullptr; - if (arg->isKind(PNK_ASSIGN)) { - bindingElement = arg->as().left(); - initializer = arg->as().right(); + if (arg->isKind(PNK_ASSIGN) || arg->isKind(PNK_INITPROP)) { + bindingElement = arg->as().left(); + initializer = arg->as().right(); } + bool hasInitializer = !!initializer; + bool isRest = hasRest && arg->pn_next == funBody; + bool isDestructuring = !bindingElement->isKind(PNK_NAME); // Left-hand sides are either simple names or destructuring patterns. MOZ_ASSERT(bindingElement->isKind(PNK_NAME) || @@ -8262,106 +8789,122 @@ BytecodeEmitter::emitFunctionFormalParameters(ListNode* paramsBody) bindingElement->isKind(PNK_ARRAYCOMP) || bindingElement->isKind(PNK_OBJECT)); - // The rest parameter doesn't have an initializer. - bool isRest = hasRest && arg->pn_next == funBody; - MOZ_ASSERT_IF(isRest, !initializer); + auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { + // [stack] - bool isDestructuring = !bindingElement->isKind(PNK_NAME); + if (!this->emitInitializer(initializer, bindingElement)) { + // [stack] DEFAULT + return false; + } + return true; + }; - // ES 14.1.19 says if BindingElement contains an expression in the - // production FormalParameter : BindingElement, it is evaluated in a - // new var environment. This is needed to prevent vars from escaping - // direct eval in parameter expressions. - Maybe paramExprVarScope; - if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) { - paramExprVarScope.emplace(this); - if (!paramExprVarScope->enterParameterExpressionVar(this)) - return false; - } - - // First push the RHS if there is a default expression or if it is - // rest. - - if (initializer) { - // If we have an initializer, emit the initializer and assign it - // to the argument slot. TDZ is taken care of afterwards. - MOZ_ASSERT(hasParameterExprs); - if (!emitArgOp(JSOP_GETARG, argSlot)) - return false; - if (!emit1(JSOP_DUP)) - return false; - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_STRICTEQ)) - return false; - // Emit source note to enable Ion compilation. - if (!newSrcNote(SRC_IF)) - return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) - return false; - if (!emit1(JSOP_POP)) - return false; - if (!emitInitializerInBranch(initializer, bindingElement)) - return false; - if (!emitJumpTargetAndPatch(jump)) - return false; - } else if (isRest) { - if (!emit1(JSOP_REST)) - return false; - } - - // Initialize the parameter name. - - if (isDestructuring) { - // If we had an initializer or the rest parameter, the value is - // already on the stack. - if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot)) - return false; + auto emitDestructuring = [this, &fpe, &bindingElement]() { + // [stack] ARG // If there's an parameter expression var scope, the destructuring // declaration needs to initialize the name in the function scope, // which is not the innermost scope. - if (!emitDestructuringOps(&bindingElement->as(), - paramExprVarScope - ? DestructuringFormalParameterInVarScope - : DestructuringDeclaration)) - { + if (!this->emitDestructuringOps(&bindingElement->as(), + fpe.getDestructuringFlavor())) { + // [stack] ARG return false; } - if (!emit1(JSOP_POP)) - return false; - } else if (hasParameterExprs || isRest) { - RootedAtom paramName(cx, bindingElement->name()); - NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope); + return true; + }; - NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { - return false; - } - if (hasParameterExprs) { - // If we had an initializer or a rest parameter, the value is - // already on the stack. - if (!initializer && !isRest) { - if (!emitArgOp(JSOP_GETARG, argSlot)) - return false; + if (isRest) { + if (isDestructuring) { + if (!fpe.prepareForDestructuringRest()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringRestEnd()) { + // [stack] + return false; + } + } else { + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitRest(paramName)) { + // [stack] + return false; + } + } + + continue; + } + + if (isDestructuring) { + if (hasInitializer) { + if (!fpe.prepareForDestructuringDefaultInitializer()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + if (!fpe.prepareForDestructuringDefault()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringDefaultEnd()) { + // [stack] + return false; + } + } else { + if (!fpe.prepareForDestructuring()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringEnd()) { + // [stack] + return false; } } - if (!noe.emitAssignment()) { - return false; - } - if (!emit1(JSOP_POP)) { - return false; - } + + continue; } - if (paramExprVarScope) { - if (!paramExprVarScope->leave(this)) + if (hasInitializer) { + if (!fpe.prepareForDefault()) { + // [stack] return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + RootedAtom paramName(cx, bindingElement->as().name()); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; } } + return true; } @@ -8370,6 +8913,8 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() { FunctionBox* funbox = sc->asFunctionBox(); + // [stack] + auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) { @@ -8379,14 +8924,18 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { + // [stack] return false; } if (!bce->emit1(op)) { + // [stack] THIS/ARGUMENTS return false; } if (!noe.emitAssignment()) + // [stack] THIS/ARGUMENTS return false; if (!bce->emit1(JSOP_POP)) + // [stack] return false; return true; @@ -8395,6 +8944,7 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() // Do nothing if the function doesn't have an arguments binding. if (funbox->argumentsHasLocalBinding()) { if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS)) + // [stack] return false; } @@ -8410,64 +8960,15 @@ BytecodeEmitter::emitInitializeFunctionSpecialNames() } bool -BytecodeEmitter::emitFunctionBody(ParseNode* funBody) +BytecodeEmitter::emitLexicalInitialization(NameNode* pn) { - FunctionBox* funbox = sc->asFunctionBox(); - - if (!emitTree(funBody)) - return false; - - if (funbox->needsFinalYield()) { - // If we fall off the end of a generator, do a final yield. - bool needsIteratorResult = funbox->needsIteratorResult(); - if (needsIteratorResult) { - if (!emitPrepareIteratorResult()) - return false; - } - - if (!emit1(JSOP_UNDEFINED)) - return false; - - if (needsIteratorResult) { - if (!emitFinishIteratorResult(true)) - return false; - } - - if (!emit1(JSOP_SETRVAL)) - return false; - - if (!emitGetDotGeneratorInInnermostScope()) - return false; - - // No need to check for finally blocks, etc as in EmitReturn. - if (!emitYieldOp(JSOP_FINALYIELDRVAL)) - return false; - } else { - // Non-generator functions just return |undefined|. The - // JSOP_RETRVAL emitted below will do that, except if the - // script has a finally block: there can be a non-undefined - // value in the return value slot. Make sure the return value - // is |undefined|. - if (hasTryFinally) { - if (!emit1(JSOP_UNDEFINED)) - return false; - if (!emit1(JSOP_SETRVAL)) - return false; - } - } - - if (funbox->isDerivedClassConstructor()) { - if (!emitCheckDerivedClassConstructorReturn()) - return false; - } - - return true; + return emitLexicalInitialization(pn->name()); } bool -BytecodeEmitter::emitLexicalInitialization(ParseNode* pn) +BytecodeEmitter::emitLexicalInitialization(JSAtom* name) { - NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize); + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); if (!noe.prepareForRhs()) { return false; } @@ -8491,121 +8992,161 @@ BytecodeEmitter::emitClass(ClassNode* classNode) { ClassNames* names = classNode->names(); ParseNode* heritageExpression = classNode->heritage(); - ListNode* classMethods = classNode->methodList(); + ListNode* classMembers = classNode->memberList(); - FunctionNode* constructor = nullptr; - for (ParseNode* mn : classMethods->contents()) { - ClassMethod& method = mn->as(); - ParseNode& methodName = method.name(); - if (!method.isStatic() && - (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && - methodName.as().atom() == cx->names().constructor) - { - constructor = &method.method(); - break; + ParseNode* constructor = nullptr; + for (ParseNode* classElement : classMembers->contents()) { + ParseNode* unwrappedElement = classElement; + if (unwrappedElement->is()) + unwrappedElement = unwrappedElement->as().scopeBody(); + if (unwrappedElement->is()) { + ClassMethod& method = unwrappedElement->as(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) && + methodName.as().atom() == cx->names().constructor) + { + constructor = classElement; + break; + } } } - bool savedStrictness = sc->setLocalStrictMode(true); + // [stack] - Maybe tdzCache; - Maybe emitterScope; + ClassEmitter ce(this); + RootedAtom innerName(cx); + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; if (names) { - tdzCache.emplace(this); - emitterScope.emplace(this); - if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode->scopeBindings())) + innerName = names->innerBinding()->as().atom(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->as().atom()); + MOZ_ASSERT(names->outerBinding()->as().atom() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + } + + if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) { + if (!ce.emitScope(scopeBindings->scopeBindings())) { + // [stack] return false; + } } // This is kind of silly. In order to the get the home object defined on // the constructor, we have to make it second, but we want the prototype // on top for EmitPropertyList, because we expect static properties to be // rarer. The result is a few more swaps than we would like. Such is life. - if (heritageExpression) { - if (!emitTree(heritageExpression)) + bool isDerived = !!heritageExpression; + if (isDerived) { + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE return false; - if (!emit1(JSOP_CLASSHERITAGE)) - return false; - if (!emit1(JSOP_OBJWITHPROTO)) - return false; - - // JSOP_CLASSHERITAGE leaves both protos on the stack. After - // creating the prototype, swap it to the bottom to make the - // constructor. - if (!emit1(JSOP_SWAP)) + } + if (!ce.emitDerivedClass(innerName)) { + // [stack] HERITAGE HOMEOBJ return false; + } } else { - if (!emitNewInit(JSProto_Object)) + if (!ce.emitClass(innerName)) { + // [stack] HOMEOBJ return false; + } } if (constructor) { - if (!emitFunction(constructor, !!heritageExpression)) + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. + Maybe lse; + FunctionNode* ctor; + if (constructor->is()) { + LexicalScopeNode* constructorScope = &constructor->as(); + + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(!constructorScope->isEmptyScope()); + MOZ_ASSERT(constructorScope->scopeBindings()->length == 1); + MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() == + cx->names().dotInitializers); + + // As an optimization omit the |.initializers| binding when no instance + // fields are present. + bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) { + return HasInitializer(propdef, false); + }); + if (hasInstanceFields) { + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings())) + return false; + + // Any class with field initializers will have a constructor + if (!emitCreateFieldInitializers(ce, classMembers, + FieldPlacement::Instance)) + return false; + } + ctor = &constructorScope->scopeBody()->as().method(); + } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); + ctor = &constructor->as().method(); + } + + bool needsHomeObject = ctor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (!emitFunction(ctor, isDerived, classMembers)) { + // [stack] HOMEOBJ CTOR return false; - if (constructor->funbox()->needsHomeObject()) { - if (!emit2(JSOP_INITHOMEOBJECT, 0)) + } + if (lse.isSome()) { + if (!lse->emitEnd()) { return false; + } + lse.reset(); + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; } } else { - // In the case of default class constructors, emit the start and end - // offsets in the source buffer as source notes so that when we - // actually make the constructor during execution, we can give it the - // correct toString output. - ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin); - ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end); - if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) + if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin), + Some(classNode->pn_pos.end))) { + // [stack] CTOR HOMEOBJ return false; - - JSAtom *name = names ? names->innerBinding()->as().atom() : cx->names().empty; - if (heritageExpression) { - if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) - return false; - } else { - if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) - return false; } } - if (!emit1(JSOP_SWAP)) + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) return false; - if (!emit1(JSOP_DUP2)) - return false; - if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) - return false; - if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) + if (!emitCreateFieldInitializers(ce, classMembers, FieldPlacement::Static)) return false; - RootedPlainObject obj(cx); - if (!emitPropertyList(classMethods, &obj, ClassBody)) + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) return false; - if (!emit1(JSOP_POP)) + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ return false; - - if (names) { - ParseNode* innerName = names->innerBinding(); - if (!emitLexicalInitialization(innerName)) - return false; - - // Pop the inner scope. - if (!emitterScope->leave(this)) - return false; - emitterScope.reset(); - - ParseNode* outerName = names->outerBinding(); - if (outerName) { - if (!emitLexicalInitialization(outerName)) - return false; - // Only class statements make outer bindings, and they do not leave - // themselves on the stack. - if (!emit1(JSOP_POP)) - return false; - } } - MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness)); + if (!ce.emitBinding()) { + // [stack] CTOR + return false; + } + if (!emitInitializeStaticFields(classMembers)) { + // [stack] CTOR + return false; + } + + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + return false; + } return true; } @@ -8632,8 +9173,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; case PNK_PARAMSBODY: - if (!emitFunctionFormalParametersAndBody(&pn->as())) - return false; + MOZ_ASSERT_UNREACHABLE("ParamsBody should be handled in emitFunctionScript."); break; case PNK_IF: @@ -8746,6 +9286,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: @@ -8759,12 +9300,24 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: case PNK_DIVASSIGN: case PNK_MODASSIGN: case PNK_POWASSIGN: { - AssignmentNode* assignNode = &pn->as(); - if (!emitAssignment(assignNode->left(), assignNode->getOp(), assignNode->right())) + BinaryNode* assignNode = &pn->as(); + if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->getOp(), + assignNode->left(), assignNode->right())) return false; break; } + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: { + BinaryNode* assignNode = &pn->as(); + if (!emitShortCircuitAssignment(assignNode->getKind(), assignNode->getOp(), + assignNode->left(), assignNode->right())) { + return false; + } + break; + } + case PNK_CONDITIONAL: if (!emitConditionalExpression(pn->as(), valueUsage)) return false; @@ -8955,7 +9508,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: if (!emitTree(ed->left())) return false; if (ed->right()) { - if (!emitLexicalInitialization(ed->right())) + if (!emitLexicalInitialization(&ed->right()->as())) return false; if (!emit1(JSOP_POP)) return false; @@ -9118,7 +9671,7 @@ BytecodeEmitter::emitOptionalTree( ValueUsage valueUsage /* = ValueUsage::WantValue */) { JS_CHECK_RECURSION(cx, return false); - + ParseNodeKind kind = pn->getKind(); switch (kind) { case PNK_OPTDOT: { @@ -9375,7 +9928,7 @@ AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index) ReportAllocationOverflow(cx); return false; } - + if (!notes.growBy(1)) { ReportOutOfMemory(cx); return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 9cbf6ee38b..732a1f24f8 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -14,6 +14,7 @@ #include "jsscript.h" #include "ds/InlineTable.h" +#include "frontend/DestructuringFlavor.h" #include "frontend/JumpList.h" #include "frontend/Parser.h" #include "frontend/SharedContext.h" @@ -121,9 +122,11 @@ typedef Vector BytecodeVector; typedef Vector SrcNotesVector; class CallOrNewEmitter; +class ClassEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; +class PropertyEmitter; class PropOpEmitter; class TDZCheckCache; @@ -177,6 +180,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter EmitterScope* innermostEmitterScope_; TDZCheckCache* innermostTDZCheckCache; + /* field info for enclosing class */ + FieldInitializers fieldInitializers_; + const FieldInitializers& getFieldInitializers() { return fieldInitializers_; } + #ifdef DEBUG bool unstableEmitterScope; @@ -233,10 +240,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter const EmitterMode emitterMode; + mozilla::Maybe scriptStartOffset; + // The end location of a function body that is being emitted. - uint32_t functionBodyEndPos; - // Whether functionBodyEndPos was set. - bool functionBodyEndPosSet; + mozilla::Maybe functionBodyEndPos; /* * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" @@ -246,13 +253,15 @@ struct MOZ_STACK_CLASS BytecodeEmitter */ BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, uint32_t lineNum, - EmitterMode emitterMode = Normal); + EmitterMode emitterMode = Normal, + FieldInitializers fieldInitializers = FieldInitializers::Invalid()); // An alternate constructor that uses a TokenPos for the starting // line and that sets functionBodyEndPos as well. BytecodeEmitter(BytecodeEmitter* parent, Parser* parser, SharedContext* sc, HandleScript script, Handle lazyScript, - TokenPos bodyPosition, EmitterMode emitterMode = Normal); + TokenPos bodyPosition, EmitterMode emitterMode = Normal, + FieldInitializers fieldInitializers = FieldInitializers::Invalid()); MOZ_MUST_USE bool init(); @@ -347,9 +356,14 @@ struct MOZ_STACK_CLASS BytecodeEmitter return lastOpcodeIsJumpTarget() ? current->lastTarget.offset : offset(); } - void setFunctionBodyEndPos(TokenPos pos) { - functionBodyEndPos = pos.end; - functionBodyEndPosSet = true; + void setFunctionBodyEndPos(uint32_t pos) { + functionBodyEndPos = mozilla::Some(pos); + } + + void setScriptStartOffsetIfUnset(uint32_t pos) { + if (scriptStartOffset.isNothing()) { + scriptStartOffset = mozilla::Some(pos); + } } bool reportError(ParseNode* pn, unsigned errorNumber, ...); @@ -443,6 +457,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Helper to emit JSOP_POP or JSOP_POPN. MOZ_MUST_USE bool emitPopN(unsigned n); + // Helper to emit JSOP_SWAP or JSOP_UNPICK. + MOZ_MUST_USE bool emitUnpickN(unsigned n); + // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); @@ -504,16 +521,26 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op); MOZ_MUST_USE bool emitRegExp(uint32_t index); - MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(FunctionNode* funNode, bool needsProto = false); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(FunctionNode* funNode, + bool needsProto = false, + ListNode* classContentsIfConstructor = nullptr); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode); MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj, ptrdiff_t offset); MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList); - MOZ_MUST_USE bool emitPropertyList(ListNode* obj, MutableHandlePlainObject objp, + MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListType type); + enum class FieldPlacement { Instance, Static }; + FieldInitializers setupFieldInitializers(ListNode* classMembers, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj, FieldPlacement placement); + const FieldInitializers& findFieldInitializersForCall(); + MOZ_MUST_USE bool emitInitializeInstanceFields(); + MOZ_MUST_USE bool emitInitializeStaticFields(ListNode* classMembers); + // To catch accidental misuse, emitUint16Operand/emit3 assert that they are // not used to unconditionally emit JSOP_GETLOCAL. Variable access should // instead be emitted using EmitVarOp. In special cases, when the caller @@ -590,21 +617,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt); MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode); - enum DestructuringFlavor { - // Destructuring into a declaration. - DestructuringDeclaration, - - // Destructuring into a formal parameter, when the formal parameters - // contain an expression that might be evaluated, and thus require - // this destructuring to assign not into the innermost scope that - // contains the function body's vars, but into its enclosing scope for - // parameter expressions. - DestructuringFormalParameterInVarScope, - - // Destructuring as part of an AssignmentExpression. - DestructuringAssignment - }; - // emitDestructuringLHSRef emits the lhs expression's reference. // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. @@ -681,15 +693,19 @@ struct MOZ_STACK_CLASS BytecodeEmitter // is called at compile time. MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); - MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, - FunctionPrefixKind prefixKind = FunctionPrefixKind::None); + MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name); + MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name); + MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name); + MOZ_MUST_USE bool emitSetFunctionNameFromStack(uint8_t offset); MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); - MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); - MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs); + MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, + ParseNode* lhs, ParseNode* rhs); + MOZ_MUST_USE bool emitShortCircuitAssignment(ParseNodeKind kind, JSOp op, + ParseNode* lhs, ParseNode* rhs); MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); MOZ_MUST_USE bool emitStatement(UnaryNode* exprStmt); @@ -769,21 +785,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitDo(BinaryNode* doNode); MOZ_MUST_USE bool emitWhile(BinaryNode* whileNode); - MOZ_MUST_USE bool emitFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope = nullptr); - MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, EmitterScope* headLexicalEmitterScope); - MOZ_MUST_USE bool emitForIn(ForNode* forNode, EmitterScope* headLexicalEmitterScope); - MOZ_MUST_USE bool emitForOf(ForNode* forNode, EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForIn(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForOf(ForNode* forNode, const EmitterScope* headLexicalEmitterScope); MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead); MOZ_MUST_USE bool emitBreak(PropertyName* label); MOZ_MUST_USE bool emitContinue(PropertyName* label); - MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody); MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); - MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn); - MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(NameNode* pn); + MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name); // Emit bytecode for the spread operator. // diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp new file mode 100644 index 0000000000..0abda3e450 --- /dev/null +++ b/js/src/frontend/DefaultEmitter.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "frontend/DefaultEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOP_* + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +DefaultEmitter::DefaultEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DefaultEmitter::prepareForDefault() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] VALUE + + ifUndefined_.emplace(bce_); + + if (!bce_->emit1(JSOP_DUP)) { + // [stack] VALUE VALUE + return false; + } + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] VALUE VALUE UNDEFINED + return false; + } + if (!bce_->emit1(JSOP_STRICTEQ)) { + // [stack] VALUE EQ? + return false; + } + + if (!ifUndefined_->emitThen()) { + // [stack] VALUE + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool DefaultEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULTVALUE + + if (!ifUndefined_->emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + ifUndefined_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DefaultEmitter.h b/js/src/frontend/DefaultEmitter.h new file mode 100644 index 0000000000..38acf0c17f --- /dev/null +++ b/js/src/frontend/DefaultEmitter.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#ifndef frontend_DefaultEmitter_h +#define frontend_DefaultEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/IfEmitter.h" // IfEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting default parameter or default value. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `x = 10` in `function (x = 10) {}` +// // the value of arguments[0] is on the stack +// DefaultEmitter de(this); +// de.prepareForDefault(); +// emit(10); +// de.emitEnd(); +// +class MOZ_STACK_CLASS DefaultEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe ifUndefined_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForDefault +---------+ emitEnd +-----+ + // | Start |------------------>| Default |-------->| End | + // +-------+ +---------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForDefault. + Default, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DefaultEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/DestructuringFlavor.h b/js/src/frontend/DestructuringFlavor.h new file mode 100644 index 0000000000..1a4a20bb3c --- /dev/null +++ b/js/src/frontend/DestructuringFlavor.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#ifndef frontend_DestructuringFlavor_h +#define frontend_DestructuringFlavor_h + +namespace js { +namespace frontend { + +enum DestructuringFlavor { + // Destructuring into a declaration. + DestructuringDeclaration, + + // Destructuring into a formal parameter, when the formal parameters + // contain an expression that might be evaluated, and thus require + // this destructuring to assign not into the innermost scope that + // contains the function body's vars, but into its enclosing scope for + // parameter expressions. + DestructuringFormalParameterInVarScope, + + // Destructuring as part of an AssignmentExpression. + DestructuringAssignment +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DestructuringFlavor_h */ \ No newline at end of file diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp index 2644072337..4bd8684ab2 100644 --- a/js/src/frontend/ElemOpEmitter.cpp +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -132,11 +132,11 @@ ElemOpEmitter::emitGet() bool ElemOpEmitter::prepareForRhs() { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); - MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()|| isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); - if (isSimpleAssignment()) { + if (isSimpleAssignment() || isPropInit()) { // For CompoundAssignment, SUPERBASE is already emitted by emitGet. if (isSuper()) { if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE @@ -155,7 +155,7 @@ bool ElemOpEmitter::skipObjAndKeyAndRhs() { MOZ_ASSERT(state_ == State::Start); - MOZ_ASSERT(isSimpleAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()); #ifdef DEBUG state_ = State::Rhs; @@ -203,12 +203,15 @@ ElemOpEmitter::emitDelete() bool ElemOpEmitter::emitAssignment() { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT(state_ == State::Rhs); - JSOp setOp = isSuper() - ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER - : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; + MOZ_ASSERT_IF(isPropInit(), !isSuper()); + + JSOp setOp = isPropInit() ? JSOP_INITELEM + : isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!bce_->emitElemOpBase(setOp)) { // ELEM return false; } diff --git a/js/src/frontend/ElemOpEmitter.h b/js/src/frontend/ElemOpEmitter.h index 48b3eaa43c..5eccb920e7 100644 --- a/js/src/frontend/ElemOpEmitter.h +++ b/js/src/frontend/ElemOpEmitter.h @@ -129,6 +129,7 @@ class MOZ_STACK_CLASS ElemOpEmitter PostDecrement, PreDecrement, SimpleAssignment, + PropInit, CompoundAssignment }; enum class ObjKind { @@ -176,6 +177,7 @@ class MOZ_STACK_CLASS ElemOpEmitter // | +--------+ | // | +-------------------+ // | [SimpleAssignment] | + // | [PropInit] | // | prepareForRhs v +-----+ // +--------------------->+-------------->+->| Rhs |-+ // | ^ +-----+ | @@ -225,6 +227,10 @@ class MOZ_STACK_CLASS ElemOpEmitter return kind_ == Kind::SimpleAssignment; } + MOZ_MUST_USE bool isPropInit() const { + return kind_ == Kind::PropInit; + } + MOZ_MUST_USE bool isDelete() const { return kind_ == Kind::Delete; } diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp index d6ebfe265f..76b06ba4c6 100644 --- a/js/src/frontend/EmitterScope.cpp +++ b/js/src/frontend/EmitterScope.cpp @@ -370,7 +370,7 @@ EmitterScope::appendScopeNote(BytecodeEmitter* bce) } bool -EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) +EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) const { // Lexical bindings throw ReferenceErrors if they are used before // initialization. See ES6 8.1.1.1.6. @@ -993,7 +993,7 @@ EmitterScope::enterWith(BytecodeEmitter* bce) } bool -EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) +EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const { return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); } diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h index cfee86a214..a2f54df0f9 100644 --- a/js/src/frontend/EmitterScope.h +++ b/js/src/frontend/EmitterScope.h @@ -86,7 +86,7 @@ class EmitterScope : public Nestable MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce); MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, - uint32_t slotEnd); + uint32_t slotEnd) const; public: explicit EmitterScope(BytecodeEmitter* bce); @@ -105,7 +105,7 @@ class EmitterScope : public Nestable MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc); MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc); MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce); - MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce); + MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce) const; MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 35e4b86e0b..9fb963f63e 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -351,9 +351,13 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_DIV: case PNK_MOD: case PNK_POW: + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: @@ -377,6 +381,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_OPTELEM: case PNK_OPTCALL: case PNK_NAME: + case PNK_PRIVATE_NAME: case PNK_TEMPLATE_STRING: case PNK_TEMPLATE_STRING_LIST: case PNK_TAGGED_TEMPLATE: @@ -400,8 +405,9 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FORIN: case PNK_FOROF: case PNK_FORHEAD: - case PNK_CLASSMETHOD: - case PNK_CLASSMETHODLIST: + case PNK_CLASSFIELD: + case PNK_STATICCLASSBLOCK: + case PNK_CLASSMEMBERLIST: case PNK_CLASSNAMES: case PNK_NEWTARGET: case PNK_IMPORT_META: @@ -1679,6 +1685,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo return true; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(pn->is()); @@ -1746,6 +1753,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_ARRAYPUSH: case PNK_MUTATEPROTO: case PNK_COMPUTED_NAME: + case PNK_STATICCLASSBLOCK: case PNK_SPREAD: case PNK_EXPORT: case PNK_VOID: @@ -1810,7 +1818,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_OBJECT: case PNK_ARRAYCOMP: case PNK_STATEMENTLIST: - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: case PNK_CATCHLIST: case PNK_TEMPLATE_STRING_LIST: case PNK_VAR: @@ -1875,9 +1883,13 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_SWITCH: case PNK_COLON: + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITANDASSIGN: case PNK_BITXORASSIGN: @@ -1902,6 +1914,16 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda); } + case PNK_CLASSFIELD: { + ClassField* node = &pn->as(); + if (node->initializer()) { + if (!Fold(cx, node->unsafeRightReference(), parser, inGenexpLambda)) { + return false; + } + } + return true; + } + case PNK_NEWTARGET: case PNK_IMPORT_META:{ #ifdef DEBUG diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index a9f7c6de09..14733d74f2 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -367,11 +367,13 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return literal; } - ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { - return new_(name, heritage, methodBlock, pos); + ClassNodeType newClass(Node name, Node heritage, LexicalScopeNodeType memberBlock, + const TokenPos& pos) + { + return new_(name, heritage, memberBlock, pos); } - ListNodeType newClassMethodList(uint32_t begin) { - return new_(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); + ListNodeType newClassMemberList(uint32_t begin) { + return new_(PNK_CLASSMEMBERLIST, TokenPos(begin, begin + 1)); } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return new_(outer, inner, pos); @@ -457,19 +459,37 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return true; } - MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType methodList, Node key, FunctionNodeType funNode, - JSOp op, bool isStatic) + MOZ_MUST_USE ClassMethod* newClassMethodDefinition(Node key, FunctionNodeType funNode, + JSOp op, bool isStatic) { - MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST)); - MOZ_ASSERT(key->isKind(PNK_NUMBER) || - key->isKind(PNK_OBJECT_PROPERTY_NAME) || - key->isKind(PNK_STRING) || - key->isKind(PNK_COMPUTED_NAME)); + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); - ClassMethod* classMethod = new_(key, funNode, op, isStatic); - if (!classMethod) - return false; - methodList->append(classMethod); + return new_(key, funNode, op, isStatic); + } + + MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) + { + MOZ_ASSERT(isUsableAsObjectPropertyName(name)); + + return new_(name, initializer, isStatic); + } + + MOZ_MUST_USE StaticClassBlock* newStaticClassBlock(FunctionNodeType block) + { + return new_(block); + } + + MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) + { + MOZ_ASSERT(memberList->isKind(PNK_CLASSMEMBERLIST)); + // Constructors can be surrounded by LexicalScopes. + MOZ_ASSERT(member->isKind(PNK_CLASSMETHOD) || + member->isKind(PNK_CLASSFIELD) || + member->isKind(PNK_STATICCLASSBLOCK) || + (member->isKind(PNK_LEXICALSCOPE) && + member->as().scopeBody()->isKind(PNK_CLASSMETHOD))); + + addList(/* list = */ memberList, /* kid = */ member); return true; } @@ -732,8 +752,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) pn->setDirectRHSAnonFunction(true); } - FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind) { - return new_(syntaxKind, pos()); + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { + return new_(syntaxKind, pos); } bool setComprehensionLambdaBody(FunctionNodeType funNode, ListNodeType body) { @@ -819,6 +839,13 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node->isKind(PNK_SUPERBASE); } + bool isUsableAsObjectPropertyName(ParseNode* node) { + return node->isKind(PNK_NUMBER) || + node->isKind(PNK_OBJECT_PROPERTY_NAME) || + node->isKind(PNK_STRING) || + node->isKind(PNK_COMPUTED_NAME); + } + inline MOZ_MUST_USE bool finishInitializerAssignment(NameNodeType nameNode, Node init); void setBeginPosition(Node pn, Node oth) { @@ -853,9 +880,9 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_(kind, op, pos()); } - ListNodeType newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) { + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos, JSOp op = JSOP_NOP) { MOZ_ASSERT(!isDeclarationKind(kind)); - return new_(kind, op, TokenPos(begin, begin + 1)); + return new_(kind, op, pos); } ListNodeType newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp new file mode 100644 index 0000000000..16c0e1fb06 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.cpp @@ -0,0 +1,1027 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "frontend/FunctionEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jsscript.h" // JSScript + +#include "builtin/ModuleObject.h" // ModuleObject +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/Parser.h" // BindingIter +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "vm/AsyncFunction.h" // AsyncFunctionResolveKind +#include "vm/Opcodes.h" // JSOP_* +#include "vm/Scope.h" // BindingKind +#include "wasm/AsmJS.h" // IsAsmJSModule + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Some; + +FunctionEmitter::FunctionEmitter(BytecodeEmitter* bce_, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, + bool isHoisted) + : bce_(bce_), + funbox_(funbox), + fun_(bce_->cx, funbox_->function()), + name_(bce_->cx, fun_->explicitName()), + syntaxKind_(syntaxKind), + isHoisted_(isHoisted) +{ + MOZ_ASSERT_IF(fun_->isInterpretedLazy(), fun_->lazyScript()); +} + +bool FunctionEmitter::interpretedCommon() +{ + // Mark as singletons any function which will only be executed once, or + // which is inner to a lambda we only expect to run once. In the latter + // case, if the lambda runs multiple times then CloneFunctionObject will + // make a deep clone of its contents. + bool singleton = bce_->checkRunOnceContext(); + if (!JSFunction::setTypeForScriptedFunction(bce_->cx, fun_, singleton)) + return false; + + SharedContext* outersc = bce_->sc; + if (outersc->isFunctionBox()) + outersc->asFunctionBox()->setHasInnerFunctions(); + + return true; +} + +bool FunctionEmitter::prepareForNonLazy() +{ + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(fun_->isInterpreted()); + MOZ_ASSERT(!fun_->isInterpretedLazy()); + MOZ_ASSERT(!funbox_->wasEmitted); + + // [stack] + + funbox_->wasEmitted = true; + + if (!interpretedCommon()) + return false; + + MOZ_ASSERT_IF(bce_->sc->strict(), funbox_->strictScript); + +#ifdef DEBUG + state_ = State::NonLazy; +#endif + return true; +} + +bool FunctionEmitter::emitNonLazyEnd() { + MOZ_ASSERT(state_ == State::NonLazy); + + // [stack] + + if (!emitFunction()) { + // [stack] FUN? + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitLazy() { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(fun_->isInterpreted()); + MOZ_ASSERT(fun_->isInterpretedLazy()); + MOZ_ASSERT(!funbox_->wasEmitted); + + // [stack] + + funbox_->wasEmitted = true; + + if (!interpretedCommon()) + return false; + + // We need to update the static scope chain regardless of whether + // the LazyScript has already been initialized, due to the case + // where we previously successfully compiled an inner function's + // lazy script but failed to compile the outer script after the + // fact. If we attempt to compile the outer script again, the + // static scope chain will be newly allocated and will mismatch + // the previously compiled LazyScript's. + ScriptSourceObject* source = &bce_->script->sourceObject()->as(); + fun_->lazyScript()->setEnclosingScopeAndSource(bce_->innermostScope(), source); + if (bce_->emittingRunOnceLambda) + fun_->lazyScript()->setTreatAsRunOnce(); + + if (!emitFunction()) { + // [stack] FUN? + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAgain() +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(funbox_->wasEmitted); + MOZ_ASSERT_IF(fun_->hasScript(), fun_->nonLazyScript()); + + // [stack] + + // Annex B block-scoped functions are hoisted like any other assignment + // that assigns the function to the outer 'var' binding. + if (!funbox_->isAnnexB) { +#ifdef DEBUG + state_ = State::End; +#endif + return true; + } + + // Get the location of the 'var' binding in the body scope. The + // name must be found, else there is a bug in the Annex B handling + // in Parser. + // + // In sloppy eval contexts, this location is dynamic. + Maybe lhsLoc = bce_->locationOfNameBoundInScope(name_, bce_->varEmitterScope); + + // If there are parameter expressions, the var name could be a + // parameter. + if (!lhsLoc && bce_->sc->isFunctionBox() && bce_->sc->asFunctionBox()->hasExtraBodyVarScope()) + lhsLoc = bce_->locationOfNameBoundInScope(name_, bce_->varEmitterScope->enclosingInFrame()); + + if (!lhsLoc) { + lhsLoc = Some(NameLocation::DynamicAnnexBVar()); + } else { + MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || + lhsLoc->bindingKind() == BindingKind::FormalParameter || + (lhsLoc->bindingKind() == BindingKind::Let && + bce_->sc->asFunctionBox()->hasParameterExprs)); + } + + NameOpEmitter noe(bce_, name_, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + return false; + } + if (!bce_->emitGetName(name_)) { + return false; + } + if (!noe.emitAssignment()) + return false; + if (!bce_->emit1(JSOP_POP)) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAsmJSModule() + { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(!funbox_->wasEmitted); + MOZ_ASSERT(IsAsmJSModule(fun_)); + + // [stack] + + funbox_->wasEmitted = true; + + if (!emitFunction()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitFunction() +{ + // Make the function object a literal in the outer script's pool. + unsigned index = bce_->objectList.add(funbox_); + + // [stack] + + if (!isHoisted_) { + return emitNonHoisted(index); + // [stack] FUN? + } + + bool topLevelFunction; + if (bce_->sc->isFunctionBox() || + (bce_->sc->isEvalContext() && bce_->sc->strict())) { + // No nested functions inside other functions are top-level. + topLevelFunction = false; + } else { + // In sloppy eval scripts, top-level functions are accessed dynamically. + // In global and module scripts, top-level functions are those bound in + // the var scope. + NameLocation loc = bce_->lookupName(name_); + topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || + loc.bindingKind() == BindingKind::Var; + } + + if (topLevelFunction) { + return emitTopLevelFunction(index); + // [stack] + } + + return emitHoisted(index); + // [stack] +} + +bool FunctionEmitter::emitNonHoisted(unsigned index) +{ + // Non-hoisted functions simply emit their respective op. + + // [stack] + + // JSOP_LAMBDA_ARROW is always preceded by a opcode that pushes new.target. + // See below. + MOZ_ASSERT(fun_->isArrow() == (syntaxKind_ == FunctionSyntaxKind::Arrow)); + + bool needsProto = syntaxKind_ == FunctionSyntaxKind::DerivedClassConstructor; + + if (funbox_->isAsync()) { + MOZ_ASSERT(!needsProto); + return bce_->emitAsyncWrapper(index, funbox_->needsHomeObject(), fun_->isArrow(), + fun_->isStarGenerator()); + } + + if (fun_->isArrow()) { + if (!emitNewTargetForArrow()) { + // [stack] NEW.TARGET/NULL + return false; + } + } + + if (needsProto) { + // [stack] PROTO + if (!bce_->emitIndex32(JSOP_FUNWITHPROTO, index)) { + // [stack] FUN + return false; + } + return true; + } + + // This is a FunctionExpression, ArrowFunctionExpression, or class + // constructor. Emit the single instruction (without location info). + JSOp op = syntaxKind_ == FunctionSyntaxKind::Arrow ? JSOP_LAMBDA_ARROW + : JSOP_LAMBDA; + if (!bce_->emitIndex32(op, index)) { + // [stack] FUN + return false; + } + + return true; +} + +bool FunctionEmitter::emitHoisted(unsigned index) +{ + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + + // [stack] + + // For functions nested within functions and blocks, make a lambda and + // initialize the binding name of the function in the current scope. + + NameOpEmitter noe(bce_, name_, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + if (funbox_->isAsync()) { + if (!bce_->emitAsyncWrapper(index, /* needsHomeObject = */ false, + /* isArrow = */ false, funbox_->isStarGenerator())) + { + return false; + } + } else { + if (!bce_->emitIndexOp(JSOP_LAMBDA, index)) { + return false; + } + } + + if (!noe.emitAssignment()) { + // [stack] FUN + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +bool FunctionEmitter::emitTopLevelFunction(unsigned index) +{ + // [stack] + + if (bce_->sc->isModuleContext()) { + // For modules, we record the function and instantiate the binding + // during ModuleInstantiate(), before the script is run. + + JS::Rooted module(bce_->cx, + bce_->sc->asModuleContext()->module()); + if (!module->noteFunctionDeclaration(bce_->cx, name_, fun_)) + return false; + return true; + } + + MOZ_ASSERT(bce_->sc->isGlobalContext() || bce_->sc->isEvalContext()); + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + + bce_->switchToPrologue(); + if (funbox_->isAsync()) { + if (!bce_->emitAsyncWrapper(index, fun_->isMethod(), fun_->isArrow(), + fun_->isStarGenerator())) + return false; + } else { + if (!bce_->emitIndex32(JSOP_LAMBDA, index)) + return false; + } + if (!bce_->emit1(JSOP_DEFFUN)) { + // [stack] + return false; + } + bce_->switchToMain(); + return true; +} + +bool FunctionEmitter::emitNewTargetForArrow() +{ + // [stack] + + if (bce_->sc->allowNewTarget()) { + if (!bce_->emit1(JSOP_NEWTARGET)) { + // [stack] NEW.TARGET + return false; + } + } else { + if (!bce_->emit1(JSOP_NULL)) { + // [stack] NULL + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::prepareForParameters() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (paramStart_) { + bce_->setScriptStartOffsetIfUnset(*paramStart_); + } + + // The ordering of these EmitterScopes is important. The named lambda + // scope needs to enclose the function scope needs to enclose the extra + // var scope. + + if (funbox_->namedLambdaBindings()) { + namedLambdaEmitterScope_.emplace(bce_); + if (!namedLambdaEmitterScope_->enterNamedLambda(bce_, funbox_)) + return false; + } + + /* + * Emit a prologue for run-once scripts which will deoptimize JIT code + * if the script ends up running multiple times via foo.caller related + * shenanigans. + * + * Also mark the script so that initializers created within it may be + * given more precise types. + */ + if (bce_->isRunOnceLambda()) { + bce_->script->setTreatAsRunOnce(); + MOZ_ASSERT(!bce_->script->hasRunOnce()); + + bce_->switchToPrologue(); + if (!bce_->emit1(JSOP_RUNONCE)) + return false; + bce_->switchToMain(); + } + + if (bodyEnd_) { + bce_->setFunctionBodyEndPos(*bodyEnd_); + } + + if (paramStart_) { + if (!bce_->updateLineNumberNotes(*paramStart_)) + return false; + } + + tdzCache_.emplace(bce_); + functionEmitterScope_.emplace(bce_); + + if (funbox_->hasParameterExprs) { + // There's parameter exprs, emit them in the main section. + // + // One caveat is that Debugger considers ops in the prologue to be + // unreachable (i.e. cannot set a breakpoint on it). If there are no + // parameter exprs, any unobservable environment ops (like pushing the + // call object, setting '.this', etc) need to go in the prologue, else it + // messes up breakpoint tests. + bce_->switchToMain(); + } + + if (!functionEmitterScope_->enterFunction(bce_, funbox_)) + return false; + + if (!bce_->emitInitializeFunctionSpecialNames()) { + // [stack] + return false; + } + + if (!funbox_->hasParameterExprs) + bce_->switchToMain(); + +#ifdef DEBUG + state_ = State::Parameters; +#endif + return true; +} + +bool FunctionScriptEmitter::prepareForBody() +{ + MOZ_ASSERT(state_ == State::Parameters); + + // [stack] + + if (!emitExtraBodyVarScope()) { + // [stack] + return false; + } + + if (funbox_->function()->kind() == JSFunction::FunctionKind::ClassConstructor) { + if (!funbox_->isDerivedClassConstructor()) { + if (!bce_->emitInitializeInstanceFields()) { + // [stack] + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool FunctionScriptEmitter::emitExtraBodyVarScope() +{ + // [stack] + + if (!funbox_->hasExtraBodyVarScope()) { + return true; + } + + extraBodyVarEmitterScope_.emplace(bce_); + if (!extraBodyVarEmitterScope_->enterFunctionExtraBodyVar(bce_, funbox_)) + return false; + + if (!funbox_->extraVarScopeBindings() || !funbox_->functionScopeBindings()) + return true; + + // After emitting expressions for all parameters, copy over any formal + // parameters which have been redeclared as vars. For example, in the + // following, the var y in the body scope is 42: + // + // function f(x, y = 42) { var y; } + // + JS::Rooted name(bce_->cx); + for (BindingIter bi(*funbox_->functionScopeBindings(), true); bi; bi++) { + name = bi.name(); + + // There may not be a var binding of the same name. + if (!bce_->locationOfNameBoundInScope(name, extraBodyVarEmitterScope_.ptr())) { + continue; + } + + // The '.this' and '.generator' function special + // bindings should never appear in the extra var + // scope. 'arguments', however, may. + MOZ_ASSERT(name != bce_->cx->names().dotThis && + name != bce_->cx->names().dotGenerator); + + NameOpEmitter noe(bce_, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + NameLocation paramLoc = *bce_->locationOfNameBoundInScope(name, functionEmitterScope_.ptr()); + if (!bce_->emitGetNameAtLocation(name, paramLoc)) { + // [stack] VAL + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::emitEndBody() +{ + MOZ_ASSERT(state_ == State::Body); + + // [stack] + + if (funbox_->needsFinalYield()) { + // If we fall off the end of a generator, do a final yield. + bool needsIteratorResult = funbox_->needsIteratorResult(); + if (needsIteratorResult) { + if (!bce_->emitPrepareIteratorResult()) { + // [stack] RESULT + return false; + } + } + + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] RESULT? UNDEF + return false; + } + + if (needsIteratorResult) { + if (!bce_->emitFinishIteratorResult(true)) { + // [stack] RESULT + return false; + } + } + + if (!bce_->emit1(JSOP_SETRVAL)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + // No need to check for finally blocks, etc as in EmitReturn. + if (!bce_->emitYieldOp(JSOP_FINALYIELDRVAL)) { + // [stack] + return false; + } + } else { + // Non-generator functions just return |undefined|. The + // JSOP_RETRVAL emitted below will do that, except if the + // script has a finally block: there can be a non-undefined + // value in the return value slot. Make sure the return value + // is |undefined|. + if (bce_->hasTryFinally) { + if (!bce_->emit1(JSOP_UNDEFINED)) { + // [stack] UNDEF + return false; + } + if (!bce_->emit1(JSOP_SETRVAL)) { + // [stack] + return false; + } + } + } + + if (funbox_->isDerivedClassConstructor()) { + if (!bce_->emitCheckDerivedClassConstructorReturn()) { + // [stack] + return false; + } + } + + if (extraBodyVarEmitterScope_) { + if (!extraBodyVarEmitterScope_->leave(bce_)) + return false; + + extraBodyVarEmitterScope_.reset(); + } + + if (!functionEmitterScope_->leave(bce_)) + return false; + functionEmitterScope_.reset(); + tdzCache_.reset(); + + if (bodyEnd_) { + if (!bce_->updateSourceCoordNotes(*bodyEnd_)) { + return false; + } + } + + // Always end the script with a JSOP_RETRVAL. Some other parts of the + // codebase depend on this opcode, + // e.g. InterpreterRegs::setToEndOfScript. + if (!bce_->emit1(JSOP_RETRVAL)) { + // [stack] + return false; + } + + if (namedLambdaEmitterScope_) { + if (!namedLambdaEmitterScope_->leave(bce_)) + return false; + namedLambdaEmitterScope_.reset(); + } + +#ifdef DEBUG + state_ = State::EndBody; +#endif + return true; +} + +bool FunctionScriptEmitter::initScript() +{ + MOZ_ASSERT(state_ == State::EndBody); + + if (!JSScript::fullyInitFromEmitter(bce_->cx, bce_->script, bce_)) { + return false; + } + + bce_->tellDebuggerAboutCompiledScript(bce_->cx); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce_, + FunctionBox* funbox) + : bce_(bce_), + funbox_(funbox), + functionEmitterScope_(bce_->innermostEmitterScope()) {} + +bool FunctionParamsEmitter::emitSimple(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (funbox_->hasParameterExprs) { + if (!bce_->emitArgOp(JSOP_GETARG, argSlot_)) { + // [stack] ARG + return false; + } + + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + } + + argSlot_++; + return true; +} + +bool FunctionParamsEmitter::prepareForDefault() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDefaultEnd(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuring() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + + if (!bce_->emitArgOp(JSOP_GETARG, argSlot_)) { + // [stack] ARG + return false; + } + +#ifdef DEBUG + state_ = State::Destructuring; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringEnd() +{ + MOZ_ASSERT(state_ == State::Destructuring); + + // [stack] ARG + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefaultInitializer() +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringDefaultInitializer; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefault() +{ + MOZ_ASSERT(state_ == State::DestructuringDefaultInitializer); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringDefault; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringDefaultEnd() +{ + MOZ_ASSERT(state_ == State::DestructuringDefault); + + // [stack] ARG/DEFAULT + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!leaveParameterExpressionVarScope()) { + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::emitRest(JS::Handle paramName) +{ + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!emitRestArray()) { + // [stack] REST + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringRest() + { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!enterParameterExpressionVarScope()) { + return false; + } + if (!emitRestArray()) { + // [stack] REST + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringRest; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringRestEnd() +{ + MOZ_ASSERT(state_ == State::DestructuringRest); + + // [stack] REST + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!leaveParameterExpressionVarScope()) + return false; + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::enterParameterExpressionVarScope() +{ + if (!funbox_->hasDirectEvalInParameterExpr) + return true; + + // ES 14.1.19 says if BindingElement contains an expression in the + // production FormalParameter : BindingElement, it is evaluated in a + // new var environment. This is needed to prevent vars from escaping + // direct eval in parameter expressions. + paramExprVarEmitterScope_.emplace(bce_); + if (!paramExprVarEmitterScope_->enterParameterExpressionVar(bce_)) + return false; + return true; +} + +bool FunctionParamsEmitter::leaveParameterExpressionVarScope() +{ + if (!paramExprVarEmitterScope_) + return true; + + if (!paramExprVarEmitterScope_->leave(bce_)) + return false; + paramExprVarEmitterScope_.reset(); + + return true; +} + +bool FunctionParamsEmitter::prepareForInitializer() +{ + // [stack] + + // If we have an initializer, emit the initializer and assign it + // to the argument slot. TDZ is taken care of afterwards. + MOZ_ASSERT(funbox_->hasParameterExprs); + if (!bce_->emitArgOp(JSOP_GETARG, argSlot_)) { + // [stack] ARG + return false; + } + default_.emplace(bce_); + if (!default_->prepareForDefault()) { + // [stack] + return false; + } + return true; +} + +bool FunctionParamsEmitter::emitInitializerEnd() +{ + // [stack] DEFAULT + + if (!default_->emitEnd()) { + // [stack] ARG/DEFAULT + return false; + } + default_.reset(); + return true; +} + +bool FunctionParamsEmitter::emitRestArray() +{ + // [stack] + + if (!bce_->emit1(JSOP_REST)) { + // [stack] REST + return false; + } + return true; +} + +bool FunctionParamsEmitter::emitAssignment(JS::Handle paramName) +{ + // [stack] ARG + + NameLocation paramLoc = *bce_->locationOfNameBoundInScope(paramName, functionEmitterScope_); + + // RHS is already pushed in the caller side. + // Make sure prepareForRhs doesn't touch stack. + MOZ_ASSERT(paramLoc.kind() == NameLocation::Kind::ArgumentSlot || + paramLoc.kind() == NameLocation::Kind::FrameSlot || + paramLoc.kind() == NameLocation::Kind::EnvironmentCoordinate); + + NameOpEmitter noe(bce_, paramName, paramLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] ARG + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARG + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + return true; +} + +DestructuringFlavor FunctionParamsEmitter::getDestructuringFlavor() +{ + MOZ_ASSERT(state_ == State::Destructuring || + state_ == State::DestructuringDefault || + state_ == State::DestructuringRest); + + return funbox_->hasDirectEvalInParameterExpr + ? DestructuringFormalParameterInVarScope + : DestructuringDeclaration; +} diff --git a/js/src/frontend/FunctionEmitter.h b/js/src/frontend/FunctionEmitter.h new file mode 100644 index 0000000000..98d0ad1bb5 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.h @@ -0,0 +1,450 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#ifndef frontend_FunctionEmitter_h +#define frontend_FunctionEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE + +#include // uint16_t, uint32_t + +#include "jsopcode.h" +#include "jsfun.h" // JSFunction + +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/SharedContext.h" // FunctionBox +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Rooted, JS::Handle +#include "vm/String.h" // JSAtom + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting function declaration, expression, or method etc. +// +// This class handles the enclosing script's part (function object creation, +// declaration, etc). The content of the function script is handled by +// FunctionScriptEmitter and FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f() {}`, non lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.prepareForNonLazy(); +// +// // Emit script with FunctionScriptEmitter here. +// ... +// +// fe.emitNonLazyEnd(); +// +// `function f() {}`, lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.emitLazy(); +// +// `function f() {}`, emitting hoisted function again +// // See emitAgain comment for more details +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// true); +// fe.emitAgain(); +// +// `function f() { "use asm"; }` +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// false); +// fe.emitAsmJSModule(); +// +class MOZ_STACK_CLASS FunctionEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Function linked from funbox_. + JS::Rooted fun_; + + // Function's explicit name. + JS::Rooted name_; + + FunctionSyntaxKind syntaxKind_; + bool isHoisted_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +-------+ + // | + // | [non-lazy function] + // | prepareForNonLazy +---------+ emitNonLazyEnd +-----+ + // +--------------------->| NonLazy |---------------->+->| End | + // | +---------+ ^ +-----+ + // | | + // | [lazy function] | + // | emitLazy | + // +------------------------------------------------->+ + // | ^ + // | [emitting hoisted function again] | + // | emitAgain | + // +------------------------------------------------->+ + // | ^ + // | [asm.js module] | + // | emitAsmJSModule | + // +--------------------------------------------------+ + // + enum class State { + // The initial state. + Start, + + // After calling prepareForNonLazy. + NonLazy, + + // After calling emitNonLazyEnd, emitLazy, emitAgain, or emitAsmJSModule. + End + }; + State state_ = State::Start; +#endif + + public: + FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, bool isHoisted); + + MOZ_MUST_USE bool prepareForNonLazy(); + MOZ_MUST_USE bool emitNonLazyEnd(); + + MOZ_MUST_USE bool emitLazy(); + + MOZ_MUST_USE bool emitAgain(); + + MOZ_MUST_USE bool emitAsmJSModule(); + + private: + // Common code for non-lazy and lazy functions. + MOZ_MUST_USE bool interpretedCommon(); + + // Emit the function declaration, expression, method etc. + // This leaves function object on the stack for expression etc, + // and doesn't for declaration. + MOZ_MUST_USE bool emitFunction(); + + // Helper methods used by emitFunction for each case. + // `index` is the object index of the function. + MOZ_MUST_USE bool emitNonHoisted(unsigned index); + MOZ_MUST_USE bool emitHoisted(unsigned index); + MOZ_MUST_USE bool emitTopLevelFunction(unsigned index); + MOZ_MUST_USE bool emitNewTargetForArrow(); +}; + +// Class for emitting function script. +// Parameters are handled by FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a) { expr }` +// FunctionScriptEmitter fse(this, funbox_for_f, +// Some(offset_of_opening_paren), +// Some(offset_of_closing_brace)); +// fse.prepareForParameters(); +// +// // Emit parameters with FunctionParamsEmitter here. +// ... +// +// fse.prepareForBody(); +// emit(expr); +// fse.emitEnd(); +// +// // Do NameFunctions operation here if needed. +// +// fse.initScript(); +// +class MOZ_STACK_CLASS FunctionScriptEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Scope for the function name for a named lambda. + // None for anonymous function. + mozilla::Maybe namedLambdaEmitterScope_; + + // Scope for function body. + mozilla::Maybe functionEmitterScope_; + + // Scope for the extra body var. + // None if `funbox_->hasExtraBodyVarScope() == false`. + mozilla::Maybe extraBodyVarEmitterScope_; + + mozilla::Maybe tdzCache_; + + // See the comment for constructor. + mozilla::Maybe paramStart_; + mozilla::Maybe bodyEnd_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForParameters +------------+ + // | Start |---------------------->| Parameters |-+ + // +-------+ +------------+ | + // | + // +--------------------------------------------+ + // | + // | prepareForBody +------+ emitEndBody +---------+ + // +---------------->| Body |------------->| EndBody |-+ + // +------+ +---------+ | + // | + // +-------------------------------------------------+ + // | + // | initScript +-----+ + // +------------>| End | + // +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForParameters. + Parameters, + + // After calling prepareForBody. + Body, + + // After calling emitEndBody. + EndBody, + + // After calling initScript. + End + }; + State state_ = State::Start; +#endif + + public: + // Parameters are the offset in the source code for each character below: + // + // function f(a, b, ...c) { ... } + // ^ ^ + // | | + // paramStart bodyEnd + // + // Can be Nothing() if not available. + FunctionScriptEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + const mozilla::Maybe& paramStart, + const mozilla::Maybe& bodyEnd) + : bce_(bce), + funbox_(funbox), + paramStart_(paramStart), + bodyEnd_(bodyEnd) {} + + MOZ_MUST_USE bool prepareForParameters(); + MOZ_MUST_USE bool prepareForBody(); + MOZ_MUST_USE bool emitEndBody(); + + // Initialize JSScript for this function. + // WARNING: There shouldn't be any fallible operation for the function + // compilation after `initScript` call. + // See the comment inside JSScript::fullyInitFromEmitter for + // more details. + MOZ_MUST_USE bool initScript(); + + private: + MOZ_MUST_USE bool emitExtraBodyVarScope(); +}; + +// Class for emitting function parameters. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a, b=10, ...c) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.emitSimple(atom_of_a); +// +// fpe.prepareForDefault(); +// emit(10); +// fpe.emitDefaultEnd(atom_of_b); +// +// fpe.emitRest(atom_of_c); +// +// `function f([a], [b]=[1], ...[c]) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.prepareForDestructuring(); +// emit(destructuring_for_[a]); +// fpe.emitDestructuringEnd(); +// +// fpe.prepareForDestructuringDefaultInitializer(); +// emit([1]); +// fpe.prepareForDestructuringDefault(); +// emit(destructuring_for_[b]); +// fpe.emitDestructuringDefaultEnd(); +// +// fpe.prepareForDestructuringRest(); +// emit(destructuring_for_[c]); +// fpe.emitDestructuringRestEnd(); +// +class MOZ_STACK_CLASS FunctionParamsEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // The pointer to `FunctionScriptEmitter::functionEmitterScope_`, + // passed via `BytecodeEmitter::innermostEmitterScope()`. + EmitterScope* functionEmitterScope_; + + // The slot for the current parameter. + // NOTE: after emitting rest parameter, this isn't incremented. + uint16_t argSlot_ = 0; + + // DefaultEmitter for default parameter. + mozilla::Maybe default_; + + // Scope for each parameter expression. + // Populated only when there's `eval` in parameters. + mozilla::Maybe paramExprVarEmitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +----------------------------------------------------------+ + // | | + // | +-------+ | + // +->| Start |-+ | + // +-------+ | | + // | | + // +------------+ | + // | | + // | [single binding, wihtout default] | + // | emitSimple | + // +--------------------------------------------------------->+ + // | ^ + // | [single binding, with default] | + // | prepareForDefault +---------+ emitDefaultEnd | + // +--------------------->| Default |------------------------>+ + // | +---------+ ^ + // | | + // | [destructuring, without default] | + // | prepareForDestructuring +---------------+ | + // +--------------------------->| Destructuring |-+ | + // | +---------------+ | | + // | | | + // | +-----------------------------------------+ | + // | | | + // | | emitDestructuringEnd | + // | +---------------------------------------------------->+ + // | ^ + // | [destructuring, with default] | + // | prepareForDestructuringDefaultInitializer | + // +---------------------------------------------+ | + // | | | + // | +----------------------------------------+ | + // | | | + // | | +---------------------------------+ | + // | +->| DestructuringDefaultInitializer |-+ | + // | +---------------------------------+ | | + // | | | + // | +------------------------------------+ | + // | | | + // | | prepareForDestructuringDefault | + // | +-------------------------------+ | + // | | | + // | +-----------------------------+ | + // | | | + // | | +----------------------+ | + // | +->| DestructuringDefault |-+ | + // | +----------------------+ | | + // | | | + // | +-------------------------+ | + // | | | + // | | emitDestructuringDefaultEnd | + // | +---------------------------------------------->+ + // | + // | [single binding rest] + // | emitRest +-----+ + // +--------------------------------------------------------->+->| End | + // | ^ +-----+ + // | [destructuring rest] | + // | prepareForDestructuringRest +-------------------+ | + // +-------------------------------->| DestructuringRest |-+ | + // +-------------------+ | | + // | | + // +----------------------------------------------------+ | + // | | + // | emitDestructuringRestEnd | + // +-------------------------------------------------------+ + // + enum class State { + // The initial state, or after emitting non-rest parameter. + Start, + + // After calling prepareForDefault. + Default, + + // After calling prepareForDestructuring. + Destructuring, + + // After calling prepareForDestructuringDefaultInitializer. + DestructuringDefaultInitializer, + + // After calling prepareForDestructuringDefault. + DestructuringDefault, + + // After calling prepareForDestructuringRest. + DestructuringRest, + + // After calling emitRest or emitDestructuringRestEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + FunctionParamsEmitter(BytecodeEmitter* bce, FunctionBox* funbox); + + // paramName is used only when there's at least one expression in the + // paramerters (funbox_->hasParameterExprs == true). + MOZ_MUST_USE bool emitSimple(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitDefaultEnd(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDestructuring(); + MOZ_MUST_USE bool emitDestructuringEnd(); + + MOZ_MUST_USE bool prepareForDestructuringDefaultInitializer(); + MOZ_MUST_USE bool prepareForDestructuringDefault(); + MOZ_MUST_USE bool emitDestructuringDefaultEnd(); + + MOZ_MUST_USE bool emitRest(JS::Handle paramName); + + MOZ_MUST_USE bool prepareForDestructuringRest(); + MOZ_MUST_USE bool emitDestructuringRestEnd(); + + MOZ_MUST_USE DestructuringFlavor getDestructuringFlavor(); + + private: + // Enter/leave var scope for `eval` if necessary. + MOZ_MUST_USE bool enterParameterExpressionVarScope(); + MOZ_MUST_USE bool leaveParameterExpressionVarScope(); + + MOZ_MUST_USE bool prepareForInitializer(); + MOZ_MUST_USE bool emitInitializerEnd(); + + MOZ_MUST_USE bool emitRestArray(); + + MOZ_MUST_USE bool emitAssignment(JS::Handle paramName); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_FunctionEmitter_h */ diff --git a/js/src/frontend/LexicalScopeEmitter.cpp b/js/src/frontend/LexicalScopeEmitter.cpp new file mode 100644 index 0000000000..053f1ea73d --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "frontend/LexicalScopeEmitter.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter + +using namespace js; +using namespace js::frontend; + +LexicalScopeEmitter::LexicalScopeEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool LexicalScopeEmitter::emitScope(ScopeKind kind, JS::Handle bindings) + +{ + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bindings); + + tdzCache_.emplace(bce_); + emitterScope_.emplace(bce_); + if (!emitterScope_->enterLexical(bce_, kind, bindings)) + return false; + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEmptyScope() +{ + MOZ_ASSERT(state_ == State::Start); + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEnd() +{ + MOZ_ASSERT(state_ == State::Scope); + + if (emitterScope_) { + if (!emitterScope_->leave(bce_)) + return false; + emitterScope_.reset(); + } + tdzCache_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LexicalScopeEmitter.h b/js/src/frontend/LexicalScopeEmitter.h new file mode 100644 index 0000000000..2a2a76268b --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#ifndef frontend_LexicalScopeEmitter_h +#define frontend_LexicalScopeEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Handle +#include "vm/Scope.h" // ScopeKind, LexicalScope + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for lexical scope. +// +// In addition to emitting code for entering and leaving a scope, this RAII +// guard affects the code emitted for `break` and other non-structured +// control flow. See NonLocalExitControl::prepareForNonLocalJump(). +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{ ... }` -- lexical scope with no bindings +// LexicalScopeEmitter lse(this); +// lse.emitEmptyScope(); +// emit(scopeBody); +// lse.emitEnd(); +// +// `{ let a; body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Lexical, scopeBinding); +// emit(let_and_body); +// lse.emitEnd(); +// +// `catch (e) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::SimpleCatch, scopeBinding); +// emit(body); +// lse.emitEnd(); +// +// `catch ([a, b]) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Catch, scopeBinding); +// emit(body); +// lse.emitEnd(); +class MOZ_STACK_CLASS LexicalScopeEmitter +{ + BytecodeEmitter* bce_; + + mozilla::Maybe tdzCache_; + mozilla::Maybe emitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitScope +-------+ emitEnd +-----+ + // | Start |----------->| Scope |--------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitScope/emitEmptyScope. + Scope, + + // After calling emitEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + explicit LexicalScopeEmitter(BytecodeEmitter* bce); + + // Returns the scope object for non-empty scope. + const EmitterScope& emitterScope() const { + return *emitterScope_; + } + + MOZ_MUST_USE bool emitScope(ScopeKind kind, + JS::Handle bindings); + MOZ_MUST_USE bool emitEmptyScope(); + + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LexicalScopeEmitter_h */ diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index ed3361c33a..6ddb554ad7 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -83,6 +83,7 @@ class NameResolver } case PNK_NAME: + case PNK_PRIVATE_NAME: *foundName = true; return buf->append(n->as().atom()); @@ -136,6 +137,7 @@ class NameResolver return cur; switch (cur->getKind()) { + case PNK_PRIVATE_NAME: case PNK_NAME: return cur; /* found the initialized declaration */ case PNK_THIS: return cur; /* Setting a property of 'this'. */ case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */ @@ -404,6 +406,7 @@ class NameResolver break; case PNK_OBJECT_PROPERTY_NAME: + case PNK_PRIVATE_NAME: case PNK_STRING: case PNK_TEMPLATE_STRING: MOZ_ASSERT(cur->is()); @@ -498,6 +501,19 @@ class NameResolver break; } + case PNK_CLASSFIELD: { + ClassField* node = &cur->as(); + if (!resolve(&node->name(), prefix)) { + return false; + } + if (ParseNode* init = node->initializer()) { + if (!resolve(init, prefix)) { + return false; + } + } + break; + } + case PNK_ELEM: { PropertyByValue* elem = &cur->as(); if (!elem->isSuper() && !resolve(&elem->expression(), prefix)) @@ -643,7 +659,7 @@ class NameResolver if (!resolve(heritage, prefix)) return false; } - if (!resolve(classNode->methodList(), prefix)) + if (!resolve(classNode->memberList(), prefix)) return false; break; } @@ -757,7 +773,7 @@ class NameResolver } case PNK_OBJECT: - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: for (ParseNode* element : cur->as().contents()) { if (!resolve(element, prefix)) return false; diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 0000000000..bf597d0e95 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,899 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#include "frontend/ObjectEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "jsatominlines.h" // AtomToId +#include "jsgcinlines.h" // GetGCObjectKind +#include "jsobjinlines.h" // NewBuiltinClassInstance + + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "frontend/SourceNotes.h" // SRC_* +#include "gc/Heap.h" // AllocKind +#include "js/Id.h" // jsid +#include "js/Value.h" // UndefinedHandleValue +#include "vm/NativeObject.h" // NativeDefineDataProperty +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Runtime.h" // JSAtomState (cx->names()) + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) + : bce_(bce), obj_(bce->cx) {} + +bool PropertyEmitter::prepareForProtoValue(const Maybe& keyPos) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ProtoValue; +#endif + return true; +} + +bool PropertyEmitter::emitMutateProto() +{ + MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); + + // [stack] OBJ PROTO + + if (!bce_->emit1(JSOP_MUTATEPROTO)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::prepareForSpreadOperand(const Maybe& spreadPos) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (spreadPos) { + if (!bce_->updateSourceCoordNotes(*spreadPos)) + return false; + } + if (!bce_->emit1(JSOP_DUP)) { + // [stack] OBJ OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::SpreadOperand; +#endif + return true; +} + +bool PropertyEmitter::emitSpread() +{ + MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); + + // [stack] OBJ OBJ VAL + + if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { + // [stack] OBJ + return false; + } + + obj_ = nullptr; +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(const Maybe& keyPos, + bool isStatic, bool isIndexOrComputed) +{ + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (keyPos) { + if (!bce_->updateSourceCoordNotes(*keyPos)) + return false; + } + + if (isStatic_) { + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + } + + return true; +} + +bool PropertyEmitter::prepareForPropValue(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ false)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PropValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropKey(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::IndexKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropValue() +{ + MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); + + // [stack] CTOR? OBJ CTOR? KEY + +#ifdef DEBUG + propertyState_ = PropertyState::IndexValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropKey(const Maybe& keyPos, + Kind kind /* = Kind::Prototype */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + obj_ = nullptr; + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropValue() +{ + MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); + + // [stack] CTOR? OBJ CTOR? KEY + + if (!bce_->emit1(JSOP_TOID)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedValue; +#endif + return true; +} + +bool PropertyEmitter::emitInitHomeObject(FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) +{ + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::ComputedValue); + + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + + bool isAsync = kind == FunctionAsyncKind::AsyncFunction; + if (isAsync) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED + return false; + } + } + + if (!bce_->emit2(JSOP_INITHOMEOBJECT, isIndexOrComputed_ + isAsync)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN + return false; + } + if (isAsync) { + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED + return false; + } + } + +#ifdef DEBUG + if (propertyState_ == PropertyState::PropValue) { + propertyState_ = PropertyState::InitHomeObj; + } else if (propertyState_ == PropertyState::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInitProp(JS::Handle key, + bool isPropertyAnonFunctionOrClass /* = false */, + JS::Handle anonFunction /* = nullptr */) +{ + return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key, + isPropertyAnonFunctionOrClass, anonFunction); +} + +bool PropertyEmitter::emitInitGetter(JS::Handle key) +{ + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitSetter(JS::Handle key) +{ + obj_ = nullptr; + return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER, + key, false, nullptr); +} + +bool PropertyEmitter::emitInitIndexProp(bool isPropertyAnonFunctionOrClass /* = false */) +{ + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitIndexGetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, false); +} + +bool PropertyEmitter::emitInitIndexSetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed( + isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, false); +} + +bool PropertyEmitter::emitInitComputedProp(bool isPropertyAnonFunctionOrClass /* = false */) +{ + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM, + FunctionPrefixKind::None, + isPropertyAnonFunctionOrClass); +} + +bool PropertyEmitter::emitInitComputedGetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER, + FunctionPrefixKind::Get, true); +} + +bool PropertyEmitter::emitInitComputedSetter() +{ + obj_ = nullptr; + return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER, + FunctionPrefixKind::Set, true); +} + +bool PropertyEmitter::emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction) +{ + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::InitHomeObj); + + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP || + op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER || + op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER); + + // [stack] CTOR? OBJ CTOR? VAL + + uint32_t index; + if (!bce_->makeAtomIndex(key, &index)) + return false; + + if (obj_) { + MOZ_ASSERT(!IsHiddenInitOp(op)); + MOZ_ASSERT(!obj_->inDictionaryMode()); + JS::RootedId id(bce_->cx, AtomToId(key)); + if (!NativeDefineProperty(bce_->cx, obj_, id, UndefinedHandleValue, nullptr, nullptr, + JSPROP_ENUMERATE)) + return false; + if (obj_->inDictionaryMode()) + obj_ = nullptr; + } + + if (isPropertyAnonFunctionOrClass) { + MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP); + + if (anonFunction) { + if (!bce_->setFunName(anonFunction, key)) + return false; + } else { + // NOTE: This is setting the constructor's name of the class which is + // the property value. Not of the enclosing class. + if (!bce_->emitSetClassConstructorName(key)) { + // [stack] CTOR? OBJ CTOR? FUN + return false; + } + } + } + + if (!bce_->emitIndex32(op, index)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) + return false; + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitInitIndexOrComputed(JSOp op, FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass) +{ + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed); + + MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM || + op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER || + op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER); + + // [stack] CTOR? OBJ CTOR? KEY VAL + + if (isPropertyAnonFunctionOrClass) { + if (!bce_->emitDupAt(1)) { + // [stack] CTOR? OBJ CTOR? KEY FUN FUN + return false; + } + if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) { + // [stack] CTOR? OBJ CTOR? KEY FUN + return false; + } + } + + if (!bce_->emit1(op)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) + return false; + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitPopClassConstructor() +{ + if (isStatic_) { + // [stack] CTOR HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + return true; +} + +ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} + +bool ObjectEmitter::emitObject(size_t propertyCount) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + + // [stack] + + // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + // a new object and defining (in source order) each property on the object + // (or mutating the object's [[Prototype]], in the case of __proto__). + top_ = bce_->offset(); + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] OBJ + return false; + } + + // Try to construct the shape of the object as we go, so we can emit a + // JSOP_NEWOBJECT with the final shape instead. + // In the case of computed property names and indices, we cannot fix the + // shape at bytecode compile time. When the shape cannot be determined, + // |obj| is nulled out. + + // No need to do any guessing for the object kind, since we know the upper + // bound of how many properties we plan to have. + gc::AllocKind kind = gc::GetGCObjectKind(propertyCount); + obj_ = NewBuiltinClassInstance(bce_->cx, kind, TenuredObject); + if (!obj_) + return false; + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ClassEmitter::prepareForFieldInitializers(size_t numFields, bool isStatic) +{ + MOZ_ASSERT_IF(!isStatic, classState_ == ClassState::Class); + MOZ_ASSERT_IF(isStatic, classState_ == ClassState::InitConstructor); + MOZ_ASSERT(fieldState_ == FieldState::Start); + + // .initializers is a variable that stores an array of lambdas containing + // code (the initializer) for each field. Upon an object's construction, + // these lambdas will be called, defining the values. + HandlePropertyName initializers = isStatic ? bce_->cx->names().dotStaticInitializers + : bce_->cx->names().dotInitializers; + initializersAssignment_.emplace(bce_, initializers, + NameOpEmitter::Kind::Initialize); + if (!initializersAssignment_->prepareForRhs()) { + return false; + } + + if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) { + // [stack] ARRAY + return false; + } + + fieldIndex_ = 0; +#ifdef DEBUG + if (isStatic) { + classState_ = ClassState::StaticFieldInitializers; + } else { + classState_ = ClassState::InstanceFieldInitializers; + } + numFields_ = numFields; +#endif + return true; +} + +bool ClassEmitter::prepareForFieldInitializer() +{ + MOZ_ASSERT(classState_ == ClassState::InstanceFieldInitializers || + classState_ == ClassState::StaticFieldInitializers); + MOZ_ASSERT(fieldState_ == FieldState::Start); + +#ifdef DEBUG + fieldState_ = FieldState::Initializer; +#endif + return true; +} + +bool ClassEmitter::emitFieldInitializerHomeObject(bool isStatic) +{ + MOZ_ASSERT(fieldState_ == FieldState::Initializer); + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + uint8_t ofs = isStatic ? 2 + // [stack] CTOR HOMEOBJ ARRAY METHOD CTOR + : isDerived_ ? 2 : 1; + // [stack] OBJ HERITAGE? ARRAY METHOD OBJ + if (!bce_->emit2(JSOP_INITHOMEOBJECT, ofs)) { + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + +#ifdef DEBUG + fieldState_ = FieldState::InitializerWithHomeObject; +#endif + return true; +} + +bool ClassEmitter::emitStoreFieldInitializer() +{ + MOZ_ASSERT(fieldState_ == FieldState::Initializer || + fieldState_ == FieldState::InitializerWithHomeObject); + MOZ_ASSERT(fieldIndex_ < numFields_); + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + + if (!bce_->emitUint32Operand(JSOP_INITELEM_ARRAY, fieldIndex_)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + + fieldIndex_++; +#ifdef DEBUG + fieldState_ = FieldState::Start; +#endif + return true; +} + +bool ClassEmitter::emitFieldInitializersEnd() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InstanceFieldInitializers || + classState_ == ClassState::StaticFieldInitializers); + MOZ_ASSERT(fieldState_ == FieldState::Start); + MOZ_ASSERT(fieldIndex_ == numFields_); + + if (!initializersAssignment_->emitAssignment()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + initializersAssignment_.reset(); + + if (!bce_->emit1(JSOP_POP)) { + // [stack] HOMEOBJ HERITAGE? + return false; + } + +#ifdef DEBUG + if (classState_ == ClassState::InstanceFieldInitializers) { + classState_ = ClassState::InstanceFieldInitializersEnd; + } else { + classState_ = ClassState::StaticFieldInitializersEnd; + } +#endif + return true; +} + +bool ObjectEmitter::emitEnd() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(objectState_ == ObjectState::Object); + + // [stack] OBJ + + if (obj_) { + // The object survived and has a predictable shape: update the original + // bytecode. + if (!bce_->replaceNewInitWithNewObject(obj_, top_)) { + // [stack] OBJ + return false; + } + } + +#ifdef DEBUG + objectState_ = ObjectState::End; +#endif + return true; +} + +AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) +{ + savedStrictness_ = sc_->setLocalStrictMode(true); +} + +AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() +{ + if (sc_) { + restore(); + } +} + +void AutoSaveLocalStrictMode::restore() +{ + MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); + sc_ = nullptr; +} + +ClassEmitter::ClassEmitter(BytecodeEmitter* bce) + : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) +{ + isClass_ = true; +} + +bool ClassEmitter::emitScope(JS::Handle scopeBindings) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start); + + tdzCache_.emplace(bce_); + + innerScope_.emplace(bce_); + if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) + return false; + +#ifdef DEBUG + classState_ = ClassState::Scope; +#endif + return true; +} + +bool ClassEmitter::emitClass(JS::Handle name) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] + + setName(name); + isDerived_ = false; + + if (!bce_->emitNewInit(JSProto_Object)) { + // [stack] HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitDerivedClass(JS::Handle name) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + // [stack] HERITAGE + + setName(name); + isDerived_ = true; + + if (!bce_->emit1(JSOP_CLASSHERITAGE)) { + // [stack] funcProto objProto + return false; + } + if (!bce_->emit1(JSOP_OBJWITHPROTO)) { + // [stack] funcProto HOMEOBJ + return false; + } + + // JSOP_CLASSHERITAGE leaves both protos on the stack. After + // creating the prototype, swap it to the bottom to make the + // constructor. + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] HOMEOBJ funcProto + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +void ClassEmitter::setName(JS::Handle name) +{ + name_ = name; + if (!name_) + name_ = bce_->cx->names().empty; +} + +bool ClassEmitter::emitInitConstructor(bool needsHomeObject) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class || + classState_ == ClassState::InstanceFieldInitializersEnd); + + // [stack] HOMEOBJ CTOR + + if (needsHomeObject) { + if (!bce_->emit2(JSOP_INITHOMEOBJECT, 0)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, + const Maybe& classEnd) +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + if (classStart && classEnd) { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart), + ptrdiff_t(*classEnd))) { + return false; + } + } + + if (isDerived_) { + // [stack] HERITAGE PROTO + if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } else { + // [stack] HOMEOBJ + if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::initProtoAndCtor() +{ + // [stack] HOMEOBJ CTOR + + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOP_DUP2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) { + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +bool ClassEmitter::emitBinding() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InitConstructor || + classState_ == ClassState::InstanceFieldInitializersEnd || + classState_ == ClassState::StaticFieldInitializersEnd); + + // [stack] CTOR HOMEOBJ + + if (!bce_->emit1(JSOP_POP)) { + // [stack] CTOR + return false; + } + + if (name_ != bce_->cx->names().empty) { + MOZ_ASSERT(innerScope_.isSome()); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + } + + // [stack] CTOR + +#ifdef DEBUG + classState_ = ClassState::BoundName; +#endif + return true; +} + +bool ClassEmitter::emitEnd(Kind kind) +{ + MOZ_ASSERT(classState_ == ClassState::BoundName); + // [stack] CTOR + + if (innerScope_.isSome()) { + MOZ_ASSERT(tdzCache_.isSome()); + + if (!innerScope_->leave(bce_)) + return false; + innerScope_.reset(); + tdzCache_.reset(); + } else { + MOZ_ASSERT(kind == Kind::Expression); + MOZ_ASSERT(tdzCache_.isNothing()); + } + + if (kind == Kind::Declaration) { + MOZ_ASSERT(name_); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + } + + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + + strictMode_.restore(); + +#ifdef DEBUG + classState_ = ClassState::End; +#endif + return true; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 0000000000..96c9b49700 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,859 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/. */ + +#ifndef frontend_ObjectEmitter_h +#define frontend_ObjectEmitter_h + +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include // size_t, ptrdiff_t +#include // uint32_t + +#include "jsopcode.h" // JSOp +#include "jsfun.h" // JSFunction +#include "jsscript.h" // FunctionAsyncKind + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "vm/String.h" // JSAtom +#include "vm/NativeObject.h" // PlainObject +#include "vm/Scope.h" // LexicalScope + +namespace js { + +namespace frontend { + +struct BytecodeEmitter; +class SharedContext; + +// Class for emitting bytecode for object and class properties. +// See ObjectEmitter and ClassEmitter for usage. +class MOZ_STACK_CLASS PropertyEmitter +{ + public: + enum class Kind { + // Prototype property. + Prototype, + + // Class static property. + Static + }; + + protected: + BytecodeEmitter* bce_; + + // True if the object is class. + // Set by ClassEmitter. + bool isClass_ = false; + + // True if the property is class static method. + bool isStatic_ = false; + + // True if the property has computed or index key. + bool isIndexOrComputed_ = false; + + // An object which keeps the shape of this object literal. + // This fields is reset to nullptr whenever the object literal turns out to + // have at least one numeric, computed, spread or __proto__ property, or + // the object becomes dictionary mode. + // This field is used only in ObjectEmitter. + JS::Rooted obj_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +---------+ + // | + // | +------------------------------------------------------------+ + // | | | + // | | [normal property/method/accessor] | + // | v prepareForPropValue +-----------+ +------+ | + // +->+----------------------->| PropValue |-+ +->| Init |-+ + // | +-----------+ | | +------+ + // | | | + // | +----------------------------------+ +-----------+ + // | | | + // | +-+---------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +-------------+ v | + // | +--------------------->| InitHomeObj |->+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------- + | + // | | | + // | | emitInitProp | + // | | emitInitGetter | + // | | emitInitSetter | + // | +------------------------------------------------------>+ + // | ^ + // | [index property/method/accessor] | + // | prepareForIndexPropKey +----------+ | + // +-------------------------->| IndexKey |-+ | + // | +----------+ | | + // | | | + // | +-------------------------------------+ | + // | | | + // | | prepareForIndexPropValue +------------+ | + // | +------------------------->| IndexValue |-+ | + // | +------------+ | | + // | | | + // | +---------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjForIndex |---->+ | + // | +---------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexProp | + // | | emitInitIndexGetter | + // | | emitInitIndexSetter | + // | +---------------------------------------------------->+ + // | | + // | [computed property/method/accessor] | + // | prepareForComputedPropKey +-------------+ | + // +----------------------------->| ComputedKey |-+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------------+ | + // | | | + // | | prepareForComputedPropValue +---------------+ | + // | +---------------------------->| ComputedValue |-+ | + // | +---------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +------------------------+ v | + // | +--------------------->| InitHomeObjForComputed |->+ | + // | +------------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitComputedProp | + // | | emitInitComputedGetter | + // | | emitInitComputedSetter | + // | +---------------------------------------------------->+ + // | ^ + // | | + // | [__proto__] | + // | prepareForProtoValue +------------+ emitMutateProto | + // +------------------------>| ProtoValue |-------------------->+ + // | +------------+ ^ + // | | + // | [...prop] | + // | prepareForSpreadOperand +---------------+ emitSpread | + // +-------------------------->| SpreadOperand |----------------+ + // +---------------+ + enum class PropertyState { + // The initial state. + Start, + + // After calling prepareForPropValue. + PropValue, + + // After calling emitInitHomeObject, from PropValue. + InitHomeObj, + + // After calling prepareForIndexPropKey. + IndexKey, + + // prepareForIndexPropValue. + IndexValue, + + // After calling emitInitHomeObject, from IndexValue. + InitHomeObjForIndex, + + // After calling prepareForComputedPropKey. + ComputedKey, + + // prepareForComputedPropValue. + ComputedValue, + + // After calling emitInitHomeObject, from ComputedValue. + InitHomeObjForComputed, + + // After calling prepareForProtoValue. + ProtoValue, + + // After calling prepareForSpreadOperand. + SpreadOperand, + + // After calling one of emitInitProp, emitInitGetter, emitInitSetter, + // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter, + // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread. + Init, + }; + PropertyState propertyState_ = PropertyState::Start; +#endif + + public: + explicit PropertyEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // { __proto__: protoValue } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForProtoValue( + const mozilla::Maybe& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForComputedPropValue(); + + MOZ_MUST_USE bool emitInitHomeObject( + FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction); + + // @param key + // Property key + // @param isPropertyAnonFunctionOrClass + // True if the property value is an anonymous function or + // an anonymous class + // @param anonFunction + // The anonymous function object for property value + MOZ_MUST_USE bool emitInitProp( + JS::Handle key, bool isPropertyAnonFunctionOrClass = false, + JS::Handle anonFunction = nullptr); + MOZ_MUST_USE bool emitInitGetter(JS::Handle key); + MOZ_MUST_USE bool emitInitSetter(JS::Handle key); + + MOZ_MUST_USE bool emitInitIndexProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitIndexGetter(); + MOZ_MUST_USE bool emitInitIndexSetter(); + + MOZ_MUST_USE bool emitInitComputedProp( + bool isPropertyAnonFunctionOrClass = false); + MOZ_MUST_USE bool emitInitComputedGetter(); + MOZ_MUST_USE bool emitInitComputedSetter(); + + private: + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp( + const mozilla::Maybe& keyPos, bool isStatic, bool isComputed); + + // @param op + // Opcode for initializing property + // @param prefixKind + // None, Get, or Set + // @param key + // Atom of the property if the property key is not computed + // @param isPropertyAnonFunctionOrClass + // True if the property is either an anonymous function or an + // anonymous class + // @param anonFunction + // Anonymous function object for the property + MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle key, + bool isPropertyAnonFunctionOrClass, + JS::Handle anonFunction); + MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op, + FunctionPrefixKind prefixKind, + bool isPropertyAnonFunctionOrClass); + + MOZ_MUST_USE bool emitPopClassConstructor(); +}; + +// Class for emitting bytecode for object literal. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{}` +// ObjectEmitter oe(this); +// oe.emitObject(0); +// oe.emitEnd(); +// +// `{ prop: 10 }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(10); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ prop: function() {} }`, when property value is anonymous function +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function); +// oe.emitInitProp(atom_of_prop, true, function_object); +// +// oe.emitEnd(); +// +// `{ get prop() { ... }, set prop(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(2); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(Some(offset_of_prop)); +// emit(function_for_setter); +// oe.emitInitSetter(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForIndexPropKey(Some(offset_of_prop)); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(atom_of_prop); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(3); +// oe.prepareForIndexPropValue(); +// emit(function_for_setter); +// oe.emitInitIndexSetter(); +// +// oe.emitEnd(); +// +// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop3); +// oe.prepareForComputedPropValue(); +// emit(function_for_setter); +// oe.emitInitComputedSetter(); +// +// oe.emitEnd(); +// +// `{ __proto__: obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForProtoValue(Some(offset_of___proto__)); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(Some(offset_of_triple_dots)); +// emit(obj); +// oe.emitSpread(); +// oe.emitEnd(); +// +class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter +{ + private: + // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later + // when the object is known to have a fixed shape. + ptrdiff_t top_ = 0; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitObject +--------+ + // | Start |----------->| Object |-+ + // +-------+ +--------+ | + // | + // +-----------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ObjectState { + // The initial state. + Start, + + // After calling emitObject. + Object, + + // After calling emitEnd. + End, + }; + ObjectState objectState_ = ObjectState::Start; +#endif + + public: + explicit ObjectEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitObject(size_t propertyCount); + MOZ_MUST_USE bool emitEnd(); +}; + +// Save and restore the strictness. +// Used by class declaration/expression to temporarily enable strict mode. +class MOZ_RAII AutoSaveLocalStrictMode +{ + SharedContext* sc_; + bool savedStrictness_; + + public: + explicit AutoSaveLocalStrictMode(SharedContext* sc); + ~AutoSaveLocalStrictMode(); + + // Force restore the strictness now. + void restore(); +}; + +// Class for emitting bytecode for JS class. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `class {}` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(atom_of_X); +// +// ce.emitInitDefaultConstructor(Some(offset_of_class), +// Some(offset_of_closing_bracket)); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... super.f(); ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X); +// +// emit(function_for_constructor); +// // pass true if constructor contains super.prop access +// ce.emitInitConstructor(/* needsHomeObject = */ true); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { field0 = expr0; ... }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// ce.prepareForFieldInitializers(fields.length()); +// for (auto field : fields) { +// emit(field.initializer_method()); +// ce.emitStoreFieldInitializer(); +// } +// ce.emitFieldInitializersEnd(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { field0 = super.method(); ... }` +// // after emitClass/emitDerivedClass +// ce.prepareForFieldInitializers(1); +// for (auto field : fields) { +// emit(field.initializer_method()); +// if (field.initializer_contains_super_or_eval()) { +// ce.emitFieldInitializerHomeObject(); +// } +// ce.emitStoreFieldInitializer(); +// } +// ce.emitFieldInitializersEnd(); +// +// `m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(FunctionAsyncKind::Async); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_p)); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForComputedPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(p); +// ce.prepareForComputedPropValue(); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitComputedGetter(); +// +class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter +{ + public: + enum class Kind { + // Class expression. + Expression, + + // Class declaration. + Declaration, + }; + + private: + // Pseudocode for class declarations: + // + // class extends BaseExpression { + // constructor() { ... } + // ... + // } + // + // + // if defined { + // let heritage = BaseExpression; + // + // if (heritage !== null) { + // funProto = heritage; + // objProto = heritage.prototype; + // } else { + // funProto = %FunctionPrototype%; + // objProto = null; + // } + // } else { + // objProto = %ObjectPrototype%; + // } + // + // let homeObject = ObjectCreate(objProto); + // + // if defined { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCache_; + mozilla::Maybe innerScope_; + AutoSaveLocalStrictMode strictMode_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+------------------------>+-+ + // +-------+ | ^ | + // | [has scope] | | + // | emitScope +-------+ | | + // +-------------->| Scope |-+ | + // +-------+ | + // | + // +-----------------------------------+ + // | + // | emitClass +-------+ + // +-+----------------->+->| Class |-+ + // | ^ +-------+ | + // | emitDerivedClass | | + // +------------------+ | + // | + // +-------------------------------+ + // | + // | prepareForFieldInitializers(isStatic = false) + // +---------------+ + // | | + // | +--------v------------------+ + // | | InstanceFieldInitializers | + // | +---------------------------+ + // | | + // | emitFieldInitializersEnd + // | | + // | +--------v---------------------+ + // | | InstanceFieldInitializersEnd | + // | +------------------------------+ + // | | + // +<--------------+ + // | + // | + // | emitInitConstructor +-----------------+ + // +-+--------------------------->+->| InitConstructor |-+ + // | ^ +-----------------+ | + // | emitInitDefaultConstructor | | + // +----------------------------+ | + // | + // +-----------------------------------------------------+ + // | + // | prepareForFieldInitializers(isStatic = true) + // +---------------+ + // | | + // | +--------v----------------+ + // | | StaticFieldInitializers | + // | +-------------------------+ + // | | + // | | emitFieldInitializersEnd + // | | + // | +--------v-------------------+ + // | | StaticFieldInitializersEnd | + // | +----------------------------+ + // | | + // +<--------------+ + // | + // | (do PropertyEmitter operation) + // +--------------------------------+ + // | + // +-------------+ emitBinding | + // | BoundName |<-----------------+ + // +--+----------+ + // | + // | emitEnd + // | + // +--v----+ + // | End | + // +-------+ + // + enum class ClassState { + // The initial state. + Start, + + // After calling emitScope. + Scope, + + // After calling emitClass or emitDerivedClass. + Class, + + // After calling emitInitConstructor or emitInitDefaultConstructor. + InitConstructor, + + // After calling prepareForFieldInitializers(isStatic = false). + InstanceFieldInitializers, + + // After calling emitFieldInitializersEnd. + InstanceFieldInitializersEnd, + + // After calling prepareForFieldInitializers(isStatic = true). + StaticFieldInitializers, + + // After calling emitFieldInitializersEnd. + StaticFieldInitializersEnd, + + // After calling emitBinding. + BoundName, + + // After calling emitEnd. + End, + }; + ClassState classState_ = ClassState::Start; + + // The state of the fields emitter. + // + // clang-format off + // + // +-------+ + // | Start +<-----------------------------+ + // +-------+ | + // | | + // | prepareForFieldInitializer | emitStoreFieldInitializer + // v | + // +-------------+ | + // | Initializer +------------------------->+ + // +-------------+ | + // | | + // | emitFieldInitializerHomeObject | + // v | + // +---------------------------+ | + // | InitializerWithHomeObject +------------+ + // +---------------------------+ + // + // clang-format on + enum class FieldState { + // After calling prepareForFieldInitializers + // and 0 or more calls to emitStoreFieldInitializer. + Start, + + // After calling prepareForFieldInitializer + Initializer, + + // After calling emitFieldInitializerHomeObject + InitializerWithHomeObject, + }; + FieldState fieldState_ = FieldState::Start; + + size_t numFields_ = 0; +#endif + + JS::Rooted name_; + mozilla::Maybe initializersAssignment_; + size_t fieldIndex_ = 0; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitScope(JS::Handle scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + MOZ_MUST_USE bool emitClass(JS::Handle name); + MOZ_MUST_USE bool emitDerivedClass(JS::Handle name); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject); + + // Parameters are the offset in the source code for each character below: + // + // class X { foo() {} } + // ^ ^ + // | | + // | classEnd + // | + // classStart + // + MOZ_MUST_USE bool emitInitDefaultConstructor( + const mozilla::Maybe& classStart, + const mozilla::Maybe& classEnd); + + MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields, bool isStatic); + MOZ_MUST_USE bool prepareForFieldInitializer(); + MOZ_MUST_USE bool emitFieldInitializerHomeObject(bool isStatic); + MOZ_MUST_USE bool emitStoreFieldInitializer(); + MOZ_MUST_USE bool emitFieldInitializersEnd(); + + MOZ_MUST_USE bool emitBinding(); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + void setName(JS::Handle name); + MOZ_MUST_USE bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index a19bdfc6eb..7ad470865f 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -236,6 +236,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_PREDECREMENT: case PNK_POSTDECREMENT: case PNK_COMPUTED_NAME: + case PNK_STATICCLASSBLOCK: case PNK_ARRAYPUSH: case PNK_SPREAD: case PNK_MUTATEPROTO: @@ -257,9 +258,13 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) // Binary nodes with two non-null children. // All assignment and compound assignment nodes qualify. + case PNK_INITPROP: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: case PNK_BITORASSIGN: case PNK_BITXORASSIGN: case PNK_BITANDASSIGN: @@ -370,6 +375,14 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } + case PNK_CLASSFIELD: { + BinaryNode* bn = &pn->as(); + stack->push(bn->left()); + if (bn->right()) + stack->push(bn->right()); + return PushResult::Recyclable; + } + // Ternary nodes with all children non-null. case PNK_CONDITIONAL: { TernaryNode* tn = &pn->as(); @@ -494,7 +507,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_IMPORT_SPEC_LIST: case PNK_EXPORT_SPEC_LIST: case PNK_PARAMSBODY: - case PNK_CLASSMETHODLIST: + case PNK_CLASSMEMBERLIST: return PushListNodeChildren(&pn->as(), stack); // Array comprehension nodes are lists with a single child: @@ -881,6 +894,7 @@ NameNode::dump(int indent) } case PNK_NAME: + case PNK_PRIVATE_NAME: // atom() already includes the '#', no need to specially include it. case PNK_PROPERTYNAME: { if (!atom()) { fprintf(stderr, "#"); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 2f6311eace..46977ee253 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -49,6 +49,7 @@ class ObjectBox; F(CALL) \ F(ARGUMENTS) \ F(NAME) \ + F(PRIVATE_NAME) \ F(OBJECT_PROPERTY_NAME) \ F(COMPUTED_NAME) \ F(NUMBER) \ @@ -116,13 +117,16 @@ class ObjectBox; F(MUTATEPROTO) \ F(CLASS) \ F(CLASSMETHOD) \ - F(CLASSMETHODLIST) \ + F(STATICCLASSBLOCK) \ + F(CLASSFIELD) \ + F(CLASSMEMBERLIST) \ F(CLASSNAMES) \ F(NEWTARGET) \ F(POSHOLDER) \ F(SUPERBASE) \ F(SUPERCALL) \ F(SETTHIS) \ + F(INITPROP) \ F(IMPORT_META) \ F(CALL_IMPORT) \ \ @@ -168,10 +172,13 @@ class ObjectBox; F(POW) \ \ /* Assignment operators (= += -= etc.). */ \ - /* ParseNode::isAssignment assumes all these are consecutive. */ \ + /* AssignmentNode::test assumes all these are consecutive. */ \ F(ASSIGN) \ F(ADDASSIGN) \ F(SUBASSIGN) \ + F(COALESCEASSIGN) \ + F(ORASSIGN) \ + F(ANDASSIGN) \ F(BITORASSIGN) \ F(BITXORASSIGN) \ F(BITANDASSIGN) \ @@ -250,20 +257,22 @@ IsTypeofKind(ParseNodeKind kind) * PNK_CLASS (ClassNode) * kid1: PNK_CLASSNAMES for class name. can be null for anonymous class. * kid2: expression after `extends`. null if no expression - * kid3: either of - * * PNK_CLASSMETHODLIST, if anonymous class - * * PNK_LEXICALSCOPE which contains PNK_CLASSMETHODLIST as scopeBody, - * if named class + * kid3: PNK_LEXICALSCOPE which contains PNK_CLASSMEMBERLIST as scopeBody * PNK_CLASSNAMES (ClassNames) * left: Name node for outer binding, or null if the class is an expression * that doesn't create an outer binding * right: Name node for inner binding - * PNK_CLASSMETHODLIST (ListNode) - * head: list of N PNK_CLASSMETHOD nodes + * PNK_CLASSMEMBERLIST (ListNode) + * head: list of N PNK_CLASSMETHOD, PNK_CLASSFIELD or PNK_STATICCLASSBLOCK nodes * count: N >= 0 * PNK_CLASSMETHOD (ClassMethod) * name: propertyName * method: methodDefinition + * PNK_CLASSFIELD (ClassField) + * name: fieldName + * initializer: field initializer or null + * PNK_STATICCLASSBLOCK (StaticClassBlock) + * block: block initializer * PNK_MODULE (ModuleNode) * body: statement list of the module * @@ -383,11 +392,16 @@ IsTypeofKind(ParseNodeKind kind) * PNK_COMMA (ListNode) * head: list of N comma-separated exprs * count: N >= 2 - * PNK_ASSIGN (BinaryNode) + * PNK_INITPROP (BinaryNode) + * left: target of assignment, base-class setter will not be invoked + * right: value to assign + * PNK_ASSIGN (AssignmentNode) * left: target of assignment * right: value to assign - * PNK_ADDASSIGN, PNK_SUBASSIGN, PNK_BITORASSIGN, PNK_BITXORASSIGN, - * PNK_BITANDASSIGN, PNK_LSHASSIGN, PNK_RSHASSIGN, PNK_URSHASSIGN, + * PNK_ADDASSIGN, PNK_SUBASSIGN, + * PNK_COALESCEASSIGN, PNK_ORASSIGN, PNK_ANDASSIGN, + * PNK_BITORASSIGN, PNK_BITXORASSIGN, PNK_BITANDASSIGN, + * PNK_LSHASSIGN, PNK_RSHASSIGN, PNK_URSHASSIGN, * PNK_MULASSIGN, PNK_DIVASSIGN, PNK_MODASSIGN, PNK_POWASSIGN (AssignmentNode) * left: target of assignment * right: value to assign @@ -567,6 +581,8 @@ enum ParseNodeArity macro(AssignmentNode, AssignmentNodeType, asAssignment) \ macro(CaseClause, CaseClauseType, asCaseClause) \ macro(ClassMethod, ClassMethodType, asClassMethod) \ + macro(ClassField, ClassFieldType, asClassField) \ + macro(StaticClassBlock, StaticClassBlockType, asStaticClassBlock) \ macro(ClassNames, ClassNamesType, asClassNames) \ macro(ForNode, ForNodeType, asFor) \ macro(PropertyAccess, PropertyAccessType, asPropertyAccess) \ @@ -618,7 +634,10 @@ enum class FunctionSyntaxKind Expression, // A non-arrow function expression. Statement, // A named function appearing as a Statement. Arrow, - Method, + Method, // Method of a class or object. + FieldInitializer, // Field initializers desugar to methods. + StaticClassBlock, // Mostly static class blocks act similar to field initializers, however, + // there is some difference in static semantics. ClassConstructor, DerivedClassConstructor, Getter, @@ -652,6 +671,7 @@ static inline bool IsMethodDefinitionKind(FunctionSyntaxKind kind) { return kind == FunctionSyntaxKind::Method || + kind == FunctionSyntaxKind::FieldInitializer || IsConstructorKind(kind) || IsGetterKind(kind) || IsSetterKind(kind); } @@ -754,6 +774,7 @@ class ParseNode private: friend class BinaryNode; friend class ForNode; + friend class ClassField; friend class ClassMethod; friend class PropertyAccessBase; friend class SwitchStatement; @@ -761,7 +782,7 @@ class ParseNode ParseNode* right; union { unsigned iflags; /* JSITER_* flags for PNK_{COMPREHENSION,}FOR node */ - bool isStatic; /* only for PNK_CLASSMETHOD */ + bool isStatic; /* only for PNK_CLASSMETHOD and PNK_CLASSFIELD */ bool hasDefault; /* only for PNK_SWITCH */ }; } binary; @@ -1233,7 +1254,7 @@ class ListNode : public ParseNode MOZ_MUST_USE bool hasNonConstInitializer() const { MOZ_ASSERT(isKind(PNK_ARRAY) || isKind(PNK_OBJECT) || - isKind(PNK_CLASSMETHODLIST)); + isKind(PNK_CLASSMEMBERLIST)); return pn_u.list.xflags & hasNonConstInitializerBit; } @@ -1250,7 +1271,7 @@ class ListNode : public ParseNode void setHasNonConstInitializer() { MOZ_ASSERT(isKind(PNK_ARRAY) || isKind(PNK_OBJECT) || - isKind(PNK_CLASSMETHODLIST)); + isKind(PNK_CLASSMEMBERLIST)); pn_u.list.xflags |= hasNonConstInitializerBit; } @@ -1874,9 +1895,9 @@ class NullLiteral : public NullaryNode } }; -// This is only used internally, currently just for tagged templates. -// It represents the value 'undefined' (aka `void 0`), like NullLiteral -// represents the value 'null'. +// This is only used internally, currently just for tagged templates and the +// initial value of fields without initializers. It represents the value +// 'undefined' (aka `void 0`), like NullLiteral represents the value 'null'. class RawUndefinedLiteral : public NullaryNode { public: @@ -2124,6 +2145,55 @@ class ClassMethod : public BinaryNode } }; + +class ClassField : public BinaryNode +{ + public: + ClassField(ParseNode* name, ParseNode* initializer, bool isStatic) + : BinaryNode(PNK_CLASSFIELD, JSOP_NOP, + TokenPos::box(name->pn_pos, initializer->pn_pos), + name, initializer) + { + pn_u.binary.isStatic = isStatic; + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_CLASSFIELD); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + return match; + } + + ParseNode& name() const { return *left(); } + + FunctionNode* initializer() const { return &right()->as(); } + + bool isStatic() const { + return pn_u.binary.isStatic; + } +}; + +// Hold onto the function generated for a class static block like +// +// class A { +// static { /* this static block */ } +// } +// +class StaticClassBlock : public UnaryNode +{ + public: + explicit StaticClassBlock(FunctionNode* function) + : UnaryNode(PNK_STATICCLASSBLOCK, JSOP_NOP, function->pn_pos, function) { + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_STATICCLASSBLOCK); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + FunctionNode* function() const { return &kid()->as(); } +}; + + class SwitchStatement : public BinaryNode { public: @@ -2207,13 +2277,11 @@ class ClassNames : public BinaryNode class ClassNode : public TernaryNode { public: - ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock, + ClassNode(ParseNode* names, ParseNode* heritage, LexicalScopeNode* memberBlock, const TokenPos& pos) - : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock, pos) + : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, memberBlock, pos) { MOZ_ASSERT_IF(names, names->is()); - MOZ_ASSERT(methodsOrBlock->is() || - methodsOrBlock->isKind(PNK_CLASSMETHODLIST)); } static bool test(const ParseNode& node) { @@ -2228,18 +2296,14 @@ class ClassNode : public TernaryNode ParseNode* heritage() const { return kid2(); } - ListNode* methodList() const { - ParseNode* methodsOrBlock = kid3(); - if (methodsOrBlock->isKind(PNK_CLASSMETHODLIST)) - return &methodsOrBlock->as(); - - ListNode* list = &methodsOrBlock->as().scopeBody()->as(); - MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST)); + ListNode* memberList() const { + ListNode* list = &kid3()->as().scopeBody()->as(); + MOZ_ASSERT(list->isKind(PNK_CLASSMEMBERLIST)); return list; } - Handle scopeBindings() const { - ParseNode* scope = kid3(); - return scope->as().scopeBindings(); + LexicalScopeNode* scopeBindings() const { + LexicalScopeNode* scope = &kid3()->as(); + return scope->isEmptyScope() ? nullptr : scope; } }; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 1120359705..162e24c6dc 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -214,12 +214,17 @@ SharedContext::computeAllowSyntax(Scope* scope) { for (ScopeIter si(scope); si; si++) { if (si.kind() == ScopeKind::Function) { - JSFunction* fun = si.scope()->as().canonicalFunction(); + FunctionScope* funScope = &si.scope()->as(); + JSFunction* fun = funScope->canonicalFunction(); if (fun->isArrow()) continue; allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); allowSuperCall_ = fun->isDerivedClassConstructor(); + if (fun->isFieldInitializer()) { + allowSuperCall_ = false; + allowArguments_ = false; + } return; } } @@ -458,6 +463,7 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac hasParameterExprs(false), hasDirectEvalInParameterExpr(false), hasDuplicateParameters(false), + allowReturn_(true), useAsm(false), insideUseAsm(false), isAnnexB(false), @@ -514,28 +520,41 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt allowNewTarget_ = sc->allowNewTarget(); allowSuperProperty_ = sc->allowSuperProperty(); allowSuperCall_ = sc->allowSuperCall(); + allowArguments_ = sc->allowArguments(); needsThisTDZChecks_ = sc->needsThisTDZChecks(); thisBinding_ = sc->thisBinding(); } else { allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); - if (IsConstructorKind(kind)) { - auto stmt = enclosing->findInnermostStatement(); - MOZ_ASSERT(stmt); - stmt->constructorBox = this; - - if (kind == FunctionSyntaxKind::DerivedClassConstructor) { - setDerivedClassConstructor(); - allowSuperCall_ = true; - needsThisTDZChecks_ = true; - } - } - if (isGenexpLambda) thisBinding_ = sc->thisBinding(); else thisBinding_ = ThisBinding::Function; + + if (IsConstructorKind(kind)) { + auto stmt = enclosing->findInnermostStatement(); + MOZ_ASSERT(stmt); + stmt->constructorBox = this; + } + + if (kind == FunctionSyntaxKind::DerivedClassConstructor) { + setDerivedClassConstructor(); + allowSuperCall_ = true; + needsThisTDZChecks_ = true; + } + + if (kind == FunctionSyntaxKind::FieldInitializer || + kind == FunctionSyntaxKind::StaticClassBlock) { + allowArguments_ = false; + if (kind == FunctionSyntaxKind::StaticClassBlock) { + allowSuperCall_ = false; + allowReturn_ = false; + } else { + MOZ_ASSERT(kind == FunctionSyntaxKind::FieldInitializer); + setFieldInitializer(); + } + } } if (sc->inWith()) { @@ -994,12 +1013,15 @@ Parser::parse() bool ParserBase::isValidStrictBinding(PropertyName* name) { - return name != context->names().eval && - name != context->names().arguments && - name != context->names().let && - name != context->names().static_ && - name != context->names().yield && - !IsStrictReservedWord(name); + TokenKind tt = ReservedWordTokenKind(name); + if (tt == TOK_NAME) { + return name != context->names().eval && + name != context->names().arguments; + } + return tt != TOK_LET && + tt != TOK_STATIC && + tt != TOK_YIELD && + !TokenKindIsStrictReservedWord(tt); } /* @@ -2293,7 +2315,7 @@ Parser::hasUsedFunctionSpecialName(HandlePropertyName name) template bool -Parser::declareFunctionThis() +Parser::declareFunctionThis(bool canSkipLazyClosedOverBindings) { // The asm.js validator does all its own symbol-table management so, as an // optimization, avoid doing any work here. @@ -2306,10 +2328,11 @@ Parser::declareFunctionThis() HandlePropertyName dotThis = context->names().dotThis; bool declareThis; - if (handler.canSkipLazyClosedOverBindings()) + if (canSkipLazyClosedOverBindings) declareThis = funbox->function()->lazyScript()->hasThisBinding(); else - declareThis = hasUsedFunctionSpecialName(dotThis) || funbox->isDerivedClassConstructor(); + declareThis = hasUsedFunctionSpecialName(dotThis) || + funbox->function()->kind() == JSFunction::FunctionKind::ClassConstructor; if (declareThis) { ParseContext::Scope& funScope = pc->functionScope(); @@ -2538,7 +2561,7 @@ Parser::standaloneFunction(HandleFunction fun, tokenStream.ungetToken(); } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement, pos()); if (!funNode) return null(); @@ -2584,7 +2607,7 @@ Parser::standaloneFunction(HandleFunction fun, template bool -Parser::declareFunctionArgumentsObject() +Parser::declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings) { FunctionBox* funbox = pc->functionBox(); ParseContext::Scope& funScope = pc->functionScope(); @@ -2596,7 +2619,7 @@ Parser::declareFunctionArgumentsObject() HandlePropertyName argumentsName = context->names().arguments; bool tryDeclareArguments; - if (handler.canSkipLazyClosedOverBindings()) + if (canSkipLazyClosedOverBindings) tryDeclareArguments = funbox->function()->lazyScript()->shouldDeclareArguments(); else tryDeclareArguments = hasUsedFunctionSpecialName(argumentsName); @@ -2671,6 +2694,10 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan uint32_t startYieldOffset = pc->lastYieldOffset; #endif + // One might expect noteUsedName(".initializers") here when parsing a + // constructor. See Parser::classDefinition on why + // it's not here. + Node body; if (type == StatementListBody) { bool inheritedStrict = pc->sc()->strict(); @@ -2755,9 +2782,10 @@ Parser::functionBody(InHandling inHandling, YieldHandling yieldHan // finishing up the scope so these special bindings get marked as closed // over if necessary. Arrow functions don't have these bindings. if (kind != FunctionSyntaxKind::Arrow) { - if (!declareFunctionArgumentsObject()) + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); + if (!declareFunctionArgumentsObject(canSkipLazyClosedOverBindings)) return null(); - if (!declareFunctionThis()) + if (!declareFunctionThis(canSkipLazyClosedOverBindings)) return null(); } @@ -2768,7 +2796,7 @@ template JSFunction* Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - HandleObject proto) + HandleObject proto /* = nullptr */) { MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, atom != nullptr); @@ -2790,6 +2818,8 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case FunctionSyntaxKind::Method: + case FunctionSyntaxKind::FieldInitializer: + case FunctionSyntaxKind::StaticClassBlock: MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator); flags = (generatorKind == NotGenerator && asyncKind == SyncFunction ? JSFunction::INTERPRETED_METHOD @@ -3030,6 +3060,7 @@ Parser::functionArguments(YieldHandling yieldHandling, FunctionSyn bool duplicatedParam = false; bool disallowDuplicateParams = kind == FunctionSyntaxKind::Arrow || kind == FunctionSyntaxKind::Method || + kind == FunctionSyntaxKind::FieldInitializer || kind == FunctionSyntaxKind::ClassConstructor; AtomVector& positionalFormals = pc->positionalFormalParameterNames(); @@ -3582,9 +3613,17 @@ Parser::standaloneLazyFunction(HandleFunction fun, bool strict FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; if (fun->isClassConstructor()) { - syntaxKind = FunctionSyntaxKind::ClassConstructor; + if (fun->isDerivedClassConstructor()) { + syntaxKind = FunctionSyntaxKind::DerivedClassConstructor; + } else { + syntaxKind = FunctionSyntaxKind::ClassConstructor; + } } else if (fun->isMethod()) { - syntaxKind = FunctionSyntaxKind::Method; + if (fun->isFieldInitializer()) { + syntaxKind = FunctionSyntaxKind::FieldInitializer; + } else { + syntaxKind = FunctionSyntaxKind::Method; + } } else if (fun->isGetter()) { syntaxKind = FunctionSyntaxKind::Getter; } else if (fun->isSetter()) { @@ -3593,7 +3632,7 @@ Parser::standaloneLazyFunction(HandleFunction fun, bool strict syntaxKind = FunctionSyntaxKind::Arrow; } - FunctionNodeType funNode = handler.newFunction(syntaxKind); + FunctionNodeType funNode = handler.newFunction(syntaxKind, pos()); if (!funNode) return null(); @@ -3649,13 +3688,21 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, FunctionBox* funbox = pc->functionBox(); RootedFunction fun(context, funbox->function()); + if (kind == FunctionSyntaxKind::ClassConstructor || + kind == FunctionSyntaxKind::DerivedClassConstructor) { + if (!noteUsedName(context->names().dotInitializers)) + return false; + } + // See below for an explanation why arrow function parameters and arrow // function bodies are parsed with different yield/await settings. { - AwaitHandling awaitHandling = funbox->isAsync() || - (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword()) - ? AwaitIsKeyword - : AwaitIsName; + AwaitHandling awaitHandling = kind == FunctionSyntaxKind::StaticClassBlock + ? AwaitIsDisallowed + : (funbox->isAsync() || + (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword())) + ? AwaitIsKeyword + : AwaitIsName; AutoAwaitIsKeyword awaitIsKeyword(this, awaitHandling); if (!functionArguments(yieldHandling, kind, funNode)) return false; @@ -3727,6 +3774,7 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, // The same goes when parsing |await| in arrow functions. YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind()); AwaitHandling bodyAwaitHandling = GetAwaitHandling(pc->asyncKind()); + bool inheritedStrict = pc->sc()->strict(); LexicalScopeNodeType body; { AutoAwaitIsKeyword awaitIsKeyword(this, bodyAwaitHandling); @@ -3735,9 +3783,15 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, return false; } + // Revalidate the function name when we transitioned to strict mode. if ((kind == FunctionSyntaxKind::Statement || - kind == FunctionSyntaxKind::Expression) && fun->explicitName()) { - RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName()); + kind == FunctionSyntaxKind::Expression) && fun->explicitName() + && !inheritedStrict && pc->sc()->strict()) + { + MOZ_ASSERT(pc->sc()->hasExplicitUseStrict(), + "strict mode should only change when a 'use strict' directive is present"); + + PropertyName* propertyName = fun->explicitName()->asPropertyName(); YieldHandling nameYieldHandling; if (kind == FunctionSyntaxKind::Expression) { // Named lambda has binding inside it. @@ -3870,7 +3924,7 @@ Parser::functionStmt(uint32_t toStringStart, YieldHandling yieldHa return null(); } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Statement, pos()); if (!funNode) return null(); @@ -3910,7 +3964,7 @@ Parser::functionExpr(uint32_t toStringStart, InvokedPrediction inv tokenStream.ungetToken(); } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Expression, pos()); if (!funNode) return null(); @@ -4434,7 +4488,8 @@ Parser::objectBindingPattern(DeclarationKind kind, YieldHandling y TokenPos namePos = tokenStream.nextToken().pos; PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInPattern, declKind, + literal, &propType, &propAtom); if (!propName) return null(); if (propType == PropertyType::Normal) { @@ -7369,6 +7424,288 @@ JSOpFromPropertyType(PropertyType propType) } } +template +bool +Parser::classMember(YieldHandling yieldHandling, + const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, + uint32_t classStartOffset, bool hasHeritage, + ClassFields& classFields, + ListNodeType& classMembers, bool* done) +{ + *done = false; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return false; + + if (tt == TOK_RC) { + *done = true; + return true; + } + + if (tt == TOK_SEMI) + return true; + + bool isStatic = false; + if (tt == TOK_STATIC) { + if (!tokenStream.peekToken(&tt)) + return false; + + if (tt == TOK_RC) { + tokenStream.consumeKnownToken(tt); + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); + return false; + } + + if (tt == TOK_LC) { + /* Parsing static class block: static { ... } */ + FunctionNodeType staticBlockBody = staticClassBlock(classFields); + if (!staticBlockBody) + return false; + + StaticClassBlockType classBlock = handler.newStaticClassBlock(staticBlockBody); + if (!classBlock) + return false; + + return handler.addClassMemberDefinition(classMembers, classBlock); + } + + if (tt != TOK_LP) { + isStatic = true; + } else { + tokenStream.ungetToken(); + } + } else { + tokenStream.ungetToken(); + } + + uint32_t propNameOffset; + if (!tokenStream.peekOffset(&propNameOffset)) + return false; + + RootedAtom propAtom(context); + PropertyType propType; + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInClass, + /* maybeDecl = */ Nothing(), + classMembers, &propType, &propAtom); + if (!propName) + return false; + + if (propType == PropertyType::Field) { + if (isStatic) { + if (propAtom == context->names().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + } + + if (propAtom == context->names().constructor) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (!abortIfSyntaxParser()) + return false; + + if (isStatic) { + classFields.staticFields++; + } else { + classFields.instanceFields++; + } + + FunctionNodeType initializer = fieldInitializerOpt(propAtom, classFields, isStatic); + if (!initializer) + return false; + + if (!matchOrInsertSemicolonAfterExpression()) { + return false; + } + + ClassFieldType field = handler.newClassFieldDefinition(propName, initializer, isStatic); + if (!field) + return false; + + return handler.addClassMemberDefinition(classMembers, field); + } + + if (propType != PropertyType::Getter && propType != PropertyType::Setter && + propType != PropertyType::Method && propType != PropertyType::GeneratorMethod && + propType != PropertyType::AsyncMethod && propType != PropertyType::AsyncGeneratorMethod && + propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor) + { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (propType == PropertyType::Getter) + propType = PropertyType::GetterNoExpressionClosure; + if (propType == PropertyType::Setter) + propType = PropertyType::SetterNoExpressionClosure; + + bool isConstructor = !isStatic && propAtom == context->names().constructor; + if (isConstructor) { + if (propType != PropertyType::Method) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + if (classStmt.constructorBox) { + errorAt(propNameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); + return false; + } + propType = hasHeritage ? PropertyType::DerivedConstructor + : PropertyType::Constructor; + } else if (isStatic && propAtom == context->names().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + RootedAtom funName(context); + switch (propType) { + case PropertyType::GetterNoExpressionClosure: + case PropertyType::SetterNoExpressionClosure: + if (!tokenStream.isCurrentTokenType(TOK_RB)) { + funName = prefixAccessorName(propType, propAtom); + if (!funName) + return false; + } + break; + case PropertyType::Constructor: + case PropertyType::DerivedConstructor: + funName = className; + break; + default: + if (!tokenStream.isCurrentTokenType(TOK_RB)) + funName = propAtom; + } + + // When |super()| is invoked, we search for the nearest scope containing + // |.initializers| to initialize the class fields. This set-up precludes + // declaring |.initializers| in the class scope, because in some syntactic + // contexts |super()| can appear nested in a class, while actually belonging + // to an outer class definition. + // + // Example: + // class Outer extends Base { + // field = 1; + // constructor() { + // class Inner { + // field = 2; + // + // // The super() call in the computed property name mustn't access + // // Inner's |.initializers| array, but instead Outer's. + // [super()]() {} + // } + // } + // } + Maybe dotInitializersScope; + if (isConstructor && !options().selfHostingMode) { + dotInitializersScope.emplace(this); + if (!dotInitializersScope->init(pc)) + return false; + + if (!noteDeclaredName(context->names().dotInitializers, DeclarationKind::Let, pos())) + return false; + } + + + // Calling toString on constructors need to return the source text for + // the entire class. The end offset is unknown at this point in + // parsing and will be amended when class parsing finishes below. + FunctionNodeType funNode = methodDefinition(isConstructor ? classStartOffset : propNameOffset, + propType, funName); + if (!funNode) + return false; + + handler.checkAndSetIsDirectRHSAnonFunction(funNode); + + JSOp op = JSOpFromPropertyType(propType); + Node method = handler.newClassMethodDefinition(propName, funNode, op, isStatic); + if (!method) + return false; + + if (dotInitializersScope.isSome()) { + method = finishLexicalScope(*dotInitializersScope, method); + if (!method) + return false; + dotInitializersScope.reset(); + } + + return handler.addClassMemberDefinition(classMembers, method); +} + +template +bool +Parser::finishClassConstructor(const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, bool hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + const ClassFields& classFields, ListNodeType& classMembers) +{ + // Fields cannot re-use the constructor obtained via JSOP_CLASSCONSTRUCTOR or + // JSOP_DERIVEDCONSTRUCTOR due to needing to emit calls to the field + // initializers in the constructor. So, synthesize a new one. + size_t numFields = classFields.instanceFields; + if (classStmt.constructorBox == nullptr && numFields > 0) { + MOZ_ASSERT(!options().selfHostingMode); + // Unconditionally create the scope here, because it's always the + // constructor. + ParseContext::Scope dotInitializersScope(this); + if (!dotInitializersScope.init(pc)) + return false; + + if (!noteDeclaredName(context->names().dotInitializers, DeclarationKind::Let, pos())) + return false; + + // synthesizeConstructor assigns to classStmt.constructorBox + FunctionNodeType synthesizedCtor = synthesizeConstructor(className, classStartOffset, hasHeritage); + if (!synthesizedCtor) { + return false; + } + + MOZ_ASSERT(classStmt.constructorBox != nullptr); + + // Note: the *function* has the name of the class, but the *property* + // containing the function has the name "constructor" + Node constructorNameNode = handler.newObjectLiteralPropertyName(context->names().constructor, pos()); + if (!constructorNameNode) { + return false; + } + + ClassMethodType method = handler.newClassMethodDefinition(constructorNameNode, synthesizedCtor, + JSOP_INITPROP, /* isStatic = */ false); + if (!method) + return false; + + LexicalScopeNodeType scope = finishLexicalScope(dotInitializersScope, method); + + if (!handler.addClassMemberDefinition(classMembers, scope)) { + return false; + } + } + + if (FunctionBox* ctorbox = classStmt.constructorBox) { + // Amend the toStringEnd offset for the constructor now that we've + // finished parsing the class. + ctorbox->toStringEnd = classEndOffset; + + if (numFields > 0) { + // Field initialization need access to `this`. + ctorbox->setHasThisBinding(); + } + + // Set the same information, but on the lazyScript. + if (ctorbox->function()->isInterpretedLazy()) { + ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset); + + if (numFields > 0) { + ctorbox->function()->lazyScript()->setHasThisBinding(); + } + } + } + + return true; +} + template typename ParseHandler::ClassNodeType Parser::classDefinition(YieldHandling yieldHandling, @@ -7384,14 +7721,14 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!tokenStream.getToken(&tt)) return null(); - RootedPropertyName name(context); + RootedPropertyName className(context); if (TokenKindIsPossibleIdentifier(tt)) { - name = bindingIdentifier(yieldHandling); - if (!name) + className = bindingIdentifier(yieldHandling); + if (!className) return null(); } else if (classContext == ClassStatement) { if (defaultHandling == AllowDefaultName) { - name = context->names().starDefaultStar; + className = context->names().starDefaultStar; tokenStream.ungetToken(); } else { // Class statements must have a bound name @@ -7403,188 +7740,104 @@ Parser::classDefinition(YieldHandling yieldHandling, tokenStream.ungetToken(); } - // Push a ParseContext::ClassStatement to keep track of the constructor - // funbox. - ParseContext::ClassStatement classStmt(pc); - - RootedAtom propAtom(context); - - // A named class creates a new lexical scope with a const binding of the - // class name for the "inner name". - Maybe innerScopeStmt; - Maybe innerScope; - if (name) { - innerScopeStmt.emplace(pc, StatementKind::Block); - innerScope.emplace(this); - if (!innerScope->init(pc)) - return null(); - } - // Because the binding definitions keep track of their blockId, we need to // create at least the inner binding later. Keep track of the name's position // in order to provide it for the nodes created later. TokenPos namePos = pos(); - Node classHeritage = null(); - bool hasHeritage; - if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS)) - return null(); - if (hasHeritage) { - if (!tokenStream.getToken(&tt)) - return null(); - classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt); - if (!classHeritage) - return null(); - } - - if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) { - return null(); - } - - ListNodeType classMethods = handler.newClassMethodList(pos().begin); - if (!classMethods) - return null(); - - Maybe declKind = Nothing(); - for (;;) { - TokenKind tt; - if (!tokenStream.getToken(&tt)) - return null(); - if (tt == TOK_RC) - break; - - if (tt == TOK_SEMI) - continue; - - bool isStatic = false; - if (tt == TOK_STATIC) { - if (!tokenStream.peekToken(&tt)) - return null(); - if (tt == TOK_RC) { - tokenStream.consumeKnownToken(tt); - error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(tt)); - return null(); - } - - if (tt != TOK_LP) { - isStatic = true; - } else { - tokenStream.ungetToken(); - } - } else { - tokenStream.ungetToken(); - } - - uint32_t nameOffset; - if (!tokenStream.peekOffset(&nameOffset)) - return null(); - - PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom); - if (!propName) - return null(); - - if (propType != PropertyType::Getter && propType != PropertyType::Setter && - propType != PropertyType::Method && propType != PropertyType::GeneratorMethod && - propType != PropertyType::AsyncMethod && propType != PropertyType::AsyncGeneratorMethod && - propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor) - { - errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); - return null(); - } - - if (propType == PropertyType::Getter) - propType = PropertyType::GetterNoExpressionClosure; - if (propType == PropertyType::Setter) - propType = PropertyType::SetterNoExpressionClosure; - - bool isConstructor = !isStatic && propAtom == context->names().constructor; - if (isConstructor) { - if (propType != PropertyType::Method) { - errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); - return null(); - } - if (classStmt.constructorBox) { - errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); - return null(); - } - propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor; - } else if (isStatic && propAtom == context->names().prototype) { - errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); - return null(); - } - - RootedAtom funName(context); - switch (propType) { - case PropertyType::GetterNoExpressionClosure: - case PropertyType::SetterNoExpressionClosure: - if (!tokenStream.isCurrentTokenType(TOK_RB)) { - funName = prefixAccessorName(propType, propAtom); - if (!funName) - return null(); - } - break; - case PropertyType::Constructor: - case PropertyType::DerivedConstructor: - funName = name; - break; - default: - if (!tokenStream.isCurrentTokenType(TOK_RB)) - funName = propAtom; - } - - // Calling toString on constructors need to return the source text for - // the entire class. The end offset is unknown at this point in - // parsing and will be amended when class parsing finishes below. - FunctionNodeType funNode = methodDefinition(isConstructor ? classStartOffset : nameOffset, - propType, funName); - if (!funNode) - return null(); - - handler.checkAndSetIsDirectRHSAnonFunction(funNode); - - JSOp op = JSOpFromPropertyType(propType); - if (!handler.addClassMethodDefinition(classMethods, propName, funNode, op, isStatic)) - return null(); - } - - // Amend the toStringEnd offset for the constructor now that we've - // finished parsing the class. - uint32_t classEndOffset = pos().end; - if (FunctionBox* ctorbox = classStmt.constructorBox) { - if (ctorbox->function()->isInterpretedLazy()) - ctorbox->function()->lazyScript()->setToStringEnd(classEndOffset); - ctorbox->toStringEnd = classEndOffset; - } + // Push a ParseContext::ClassStatement to keep track of the constructor + // funbox. + ParseContext::ClassStatement classStmt(pc); + NameNodeType innerName; Node nameNode = null(); - Node methodsOrBlock = classMethods; - if (name) { - // The inner name is immutable. - if (!noteDeclaredName(name, DeclarationKind::Const, namePos)) + Node classHeritage = null(); + LexicalScopeNodeType classBlock = null(); + uint32_t classEndOffset; + { + // A named class creates a new lexical scope with a const binding of the + // class name for the "inner name". + ParseContext::Statement innerScopeStmt(pc, StatementKind::Block); + ParseContext::Scope innerScope(this); + if (!innerScope.init(pc)) return null(); - NameNodeType innerName = newName(name, namePos); - if (!innerName) + bool hasHeritage; + if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS)) + return null(); + if (hasHeritage) { + if (!tokenStream.getToken(&tt)) + return null(); + classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt); + if (!classHeritage) + return null(); + } + + if (!mustMatchToken(TOK_LC, JSMSG_CURLY_BEFORE_CLASS)) { + return null(); + } + + ListNodeType classMembers = handler.newClassMemberList(pos().begin); + if (!classMembers) return null(); - LexicalScopeNodeType classBlock = finishLexicalScope(*innerScope, classMethods); + ClassFields classFields{}; + for (;;) { + bool done; + if (!classMember(yieldHandling, classStmt, className, classStartOffset, hasHeritage, + classFields, classMembers, &done)) + return null(); + if (done) + break; + } + + if (classFields.instanceFieldKeys > 0) { + if (!noteDeclaredName(context->names().dotFieldKeys, DeclarationKind::Let, namePos)) + return null(); + } + + if (classFields.staticFields > 0) { + if (!noteDeclaredName(context->names().dotStaticInitializers, + DeclarationKind::Let, namePos)) + return null(); + } + + if (classFields.staticFieldKeys > 0) { + if (!noteDeclaredName(context->names().dotStaticFieldKeys, + DeclarationKind::Let, namePos)) + return null(); + } + + classEndOffset = pos().end; + if (!finishClassConstructor(classStmt, className, hasHeritage, + classStartOffset, classEndOffset, classFields, classMembers)) + return null(); + + if (className) { + // The inner name is immutable. + if (!noteDeclaredName(className, DeclarationKind::Const, namePos)) + return null(); + + innerName = newName(className, namePos); + if (!innerName) + return null(); + } + + classBlock = finishLexicalScope(innerScope, classMembers); if (!classBlock) return null(); - methodsOrBlock = classBlock; - // Pop the inner scope. - innerScope.reset(); - innerScopeStmt.reset(); + } + if (className) { NameNodeType outerName = null(); if (classContext == ClassStatement) { // The outer name is mutable. - if (!noteDeclaredName(name, DeclarationKind::Let, namePos)) + if (!noteDeclaredName(className, DeclarationKind::Let, namePos)) return null(); - outerName = newName(name, namePos); + outerName = newName(className, namePos); if (!outerName) return null(); } @@ -7596,10 +7849,429 @@ Parser::classDefinition(YieldHandling yieldHandling, MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); - return handler.newClass(nameNode, classHeritage, methodsOrBlock, + return handler.newClass(nameNode, classHeritage, classBlock, TokenPos(classStartOffset, classEndOffset)); } +template +typename ParseHandler::FunctionNodeType +Parser::synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage) +{ + FunctionSyntaxKind functionSyntaxKind = hasHeritage ? FunctionSyntaxKind::DerivedClassConstructor + : FunctionSyntaxKind::ClassConstructor; + + // Create the function object. + RootedFunction fun(context, newFunction(className, functionSyntaxKind, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction)); + if (!fun) + return null(); + + // Create the top-level field initializer node. + FunctionNodeType funNode = handler.newFunction(functionSyntaxKind, pos()); + if (!funNode) + return null(); + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = newFunctionBox(funNode, fun, classNameOffset, + directives, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, false); + if (!funbox) + return null(); + funbox->initWithEnclosingParseContext(pc, functionSyntaxKind); + funbox->setEnd(pos().end); + + // Push a ParseContext on to the stack. + ParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) + return null(); + + TokenPos synthesizedBodyPos = TokenPos(classNameOffset, classNameOffset + 1); + // Create a ListNode for the parameters + body (there are no parameters). + ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, synthesizedBodyPos); + if (!argsbody) + return null(); + handler.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->setStart(tokenStream); + + if (hasHeritage) { + // Synthesize the equivalent to `function f(...args)` + funbox->setHasRest(); + if (!notePositionalFormalParameter(funNode, context->names().args, + synthesizedBodyPos.begin, + /* disallowDuplicateParams = */ false, + /* duplicatedParam = */ nullptr)) { + return null(); + } + funbox->function()->setArgCount(1); + } else { + funbox->function()->setArgCount(0); + } + + pc->functionScope().useAsVarScope(pc); + + auto stmtList = handler.newStatementList(synthesizedBodyPos); + if (!stmtList) + return null(); + + if (!noteUsedName(context->names().dotThis)) + return null(); + + if (!noteUsedName(context->names().dotInitializers)) + return null(); + + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); + if (!declareFunctionThis(canSkipLazyClosedOverBindings)) + return null(); + + if (hasHeritage) { + // {Goanna} Need a different this-NameNode for SuperBase and SetThis or the recycling + // by ParseNodeAllocator runs into all sorts of problems because the + // same ParseNode gets cleaned up twice. + // Parser::memberExpr does the same. + NameNodeType thisNameBase = newThisName(); + if (!thisNameBase) + return null(); + + UnaryNodeType superBase = handler.newSuperBase(thisNameBase, synthesizedBodyPos); + if (!superBase) + return null(); + + ListNodeType arguments = handler.newArguments(synthesizedBodyPos); + if (!arguments) + return null(); + + NameNodeType argsNameNode = newName(context->names().args, synthesizedBodyPos); + if (!argsNameNode) + return null(); + if (!noteUsedName(context->names().args)) + return null(); + + UnaryNodeType spreadArgs = handler.newSpread(synthesizedBodyPos.begin, argsNameNode); + if (!spreadArgs) + return null(); + handler.addList(arguments, spreadArgs); + + BinaryNodeType superCall = handler.newSuperCall(superBase, arguments, /* isSpread = */ true); + if (!superCall) + return null(); + + NameNodeType thisName = newThisName(); + if (!thisName) + return null(); + + BinaryNodeType setThis = handler.newSetThis(thisName, superCall); + if (!setThis) + return null(); + + UnaryNodeType exprStatement = handler.newExprStatement(setThis, synthesizedBodyPos.end); + if (!exprStatement) + return null(); + + handler.addStatementToList(stmtList, exprStatement); + } + + auto initializerBody = finishLexicalScope(pc->varScope(), stmtList); + if (!initializerBody) + return null(); + handler.setBeginPosition(initializerBody, stmtList); + handler.setEndPosition(initializerBody, stmtList); + + handler.setFunctionBody(funNode, initializerBody); + + if (!finishFunction()) + return null(); + + // This function is asserted to set classStmt->constructorBox - however, it's + // not directly set in this function, but rather in + // initWithEnclosingParseContext. + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +Parser::staticClassBlock(ClassFields& classFields) +{ + // Both for getting-this-done, and because this will invariably be executed, + // syntax parsing should be aborted. + if (!abortIfSyntaxParser()) + return null(); + + TokenPos firstTokenPos(pos()); + + // Create the anonymous function object. + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::StaticClassBlock; + AutoAwaitIsKeyword awaitIsKeyword(this, AwaitIsDisallowed); + + RootedFunction fun(context, + newFunction(nullptr, syntaxKind, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction)); + if (!fun) + return null(); + + // Create the function node for the static class body. + FunctionNodeType funNode = handler.newFunction(syntaxKind, firstTokenPos); + if (!funNode) + return null(); + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = newFunctionBox(funNode, fun, firstTokenPos.begin, directives, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, false); + if (!funbox) + return null(); + funbox->initWithEnclosingParseContext(pc, syntaxKind); + MOZ_ASSERT(!funbox->allowSuperCall()); + MOZ_ASSERT(!funbox->allowArguments()); + MOZ_ASSERT(!funbox->allowReturn()); + + // Set start at `static` token. + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_STATIC)); + funbox->setStart(tokenStream, firstTokenPos); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc; + ParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) + return null(); + + pc->functionScope().useAsVarScope(pc); + + uint32_t start = firstTokenPos.begin; + + tokenStream.consumeKnownToken(TOK_LC); + + // Static class blocks are code-generated as if they were static field + // initializers, so we bump the staticFields count here, which ensures + // .staticInitializers is noted as used. + classFields.staticFields++; + + LexicalScopeNodeType body = functionBody(InAllowed, YieldIsKeyword, syntaxKind, + StatementListBody); + if (!body) + return null(); + + if (tokenStream.isEOF()) { + error(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK); + return null(); + } + + tokenStream.consumeKnownToken(TOK_RC, TokenStream::Operand); + + TokenPos wholeBodyPos(start, pos().end); + + handler.setEndPosition(funNode, wholeBodyPos.end); + funbox->setEnd(pos().end); + + // Create a ListNode for the parameters + body (there are no parameters). + ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, wholeBodyPos); + if (!argsbody) + return null(); + + handler.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->function()->setArgCount(0); + + if (pc->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + handler.setEndPosition(body, pos().begin); + handler.setEndPosition(funNode, pos().end); + handler.setFunctionBody(funNode, body); + + if (!finishFunction()) + return null(); + + if (!leaveInnerFunction(outerpc)) + return null(); + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +Parser::fieldInitializerOpt(HandleAtom propAtom, ClassFields& classFields, bool isStatic) +{ + bool hasInitializer = false; + if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) + return null(); + + TokenPos firstTokenPos; + if (hasInitializer) { + firstTokenPos = pos(); + } else { + // the location of the "initializer" should be a zero-width span: + // class C { + // x /* here */ ; + // } + uint32_t endPos = pos().end; + firstTokenPos = TokenPos(endPos, endPos); + } + + // Create the anonymous function object. + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer; + RootedFunction fun(context, + newFunction(nullptr, syntaxKind, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction)); + if (!fun) + return null(); + + // Create the top-level field initializer node. + FunctionNodeType funNode = handler.newFunction(syntaxKind, firstTokenPos); + if (!funNode) + return null(); + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = newFunctionBox(funNode, fun, firstTokenPos.begin, directives, + GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, false); + if (!funbox) + return null(); + funbox->initWithEnclosingParseContext(pc, syntaxKind); + MOZ_ASSERT(funbox->isFieldInitializer()); + funbox->setStart(tokenStream, firstTokenPos); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc; + ParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) + return null(); + + pc->functionScope().useAsVarScope(pc); + + Node initializerExpr; + TokenPos wholeInitializerPos; + if (hasInitializer) { + // Parse the expression for the field initializer. + { + AutoAwaitIsKeyword awaitIsKeyword(this, AwaitIsName); + initializerExpr = assignExpr(InAllowed, YieldIsName, TripledotProhibited); + if (!initializerExpr) + return null(); + } + + // In `class { x = function() {} }`, the anon function can get a name. + handler.checkAndSetIsDirectRHSAnonFunction(initializerExpr); + + wholeInitializerPos = pos(); + wholeInitializerPos.begin = firstTokenPos.begin; + } else { + initializerExpr = handler.newRawUndefinedLiteral(firstTokenPos); + if (!initializerExpr) + return null(); + wholeInitializerPos = firstTokenPos; + } + + // Update the end position of the parse node. + handler.setEndPosition(funNode, wholeInitializerPos.end); + funbox->setEnd(pos().end); + + // Create a ListNode for the parameters + body (there are no parameters) + ListNodeType argsbody = handler.newList(PNK_PARAMSBODY, wholeInitializerPos); + if (!argsbody) + return null(); + handler.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->function()->setArgCount(0); + + funbox->usesThis = true; + NameNodeType thisName = newThisName(); + if (!thisName) + return null(); + + // Build `this.field` expression. + ThisLiteralType propAssignThis = handler.newThisLiteral(wholeInitializerPos, thisName); + if (!propAssignThis) + return null(); + + Node propAssignFieldAccess; + uint32_t indexValue; + if (!propAtom) { + // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what + // .fieldKeys means and its purpose. + NameNodeType fieldKeysName = newInternalDotName(isStatic ? context->names().dotStaticFieldKeys + : context->names().dotFieldKeys); + if (!fieldKeysName) + return null(); + + double fieldKeyIndex = isStatic ? classFields.staticFieldKeys++ + : classFields.instanceFieldKeys++; + Node fieldKeyIndexNode = handler.newNumber(fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos); + if (!fieldKeyIndexNode) + return null(); + + Node fieldKeyValue = handler.newPropertyByValue(fieldKeysName, fieldKeyIndexNode, wholeInitializerPos.end); + if (!fieldKeyValue) + return null(); + + propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, fieldKeyValue, wholeInitializerPos.end); + if (!propAssignFieldAccess) + return null(); + } else if (propAtom->isIndex(&indexValue)) { + // {Goanna} Can't reuse propName here, see comment in synthesizeConstructor + Node indexNode = handler.newNumber(indexValue, DecimalPoint::NoDecimal, wholeInitializerPos); + if (!indexNode) + return null(); + + propAssignFieldAccess = handler.newPropertyByValue(propAssignThis, indexNode, wholeInitializerPos.end); + if (!propAssignFieldAccess) + return null(); + } else { + NameNodeType propAssignName = handler.newPropertyName(propAtom->asPropertyName(), wholeInitializerPos); + if (!propAssignName) + return null(); + + propAssignFieldAccess = handler.newPropertyAccess(propAssignThis, propAssignName); + if (!propAssignFieldAccess) + return null(); + } + + // Synthesize a property init. + AssignmentNodeType initializerPropInit = handler.newAssignment(PNK_INITPROP, + propAssignFieldAccess, initializerExpr, + JSOP_NOP); + if (!initializerPropInit) + return null(); + + bool canSkipLazyClosedOverBindings = handler.canSkipLazyClosedOverBindings(); + if (!declareFunctionThis(canSkipLazyClosedOverBindings)) + return null(); + + UnaryNodeType exprStatement = handler.newExprStatement(initializerPropInit, wholeInitializerPos.end); + if (!exprStatement) + return null(); + + ListNodeType statementList = handler.newStatementList(wholeInitializerPos); + if (!statementList) + return null(); + handler.addStatementToList(statementList, exprStatement); + + // Set the function's body to the field assignment. + LexicalScopeNodeType initializerBody = finishLexicalScope(pc->varScope(), statementList); + if (!initializerBody) { + return null(); + } + + handler.setFunctionBody(funNode, initializerBody); + + if (pc->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + if (!finishFunction()) + return null(); + + if (!leaveInnerFunction(outerpc)) + return null(); + + return funNode; +} + template bool Parser::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling) @@ -7801,7 +8473,7 @@ Parser::statement(YieldHandling yieldHandling) // The Return parameter is only used here, and the effect is easily // detected this way, so don't bother passing around an extra parameter // everywhere. - if (!pc->isFunctionBox()) { + if (!pc->allowReturn()) { error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str); return null(); } @@ -7987,7 +8659,7 @@ Parser::statementListItem(YieldHandling yieldHandling, // The Return parameter is only used here, and the effect is easily // detected this way, so don't bother passing around an extra parameter // everywhere. - if (!pc->isFunctionBox()) { + if (!pc->allowReturn()) { error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str); return null(); } @@ -8480,6 +9152,9 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break; case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break; case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break; + case TOK_COALESCEASSIGN: kind = PNK_COALESCEASSIGN; op = JSOP_COALESCE; break; + case TOK_ORASSIGN: kind = PNK_ORASSIGN; op = JSOP_OR; break; + case TOK_ANDASSIGN: kind = PNK_ANDASSIGN; op = JSOP_AND; break; case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break; case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break; case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break; @@ -8535,7 +9210,7 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl } } - FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Arrow); + FunctionNodeType funNode = handler.newFunction(FunctionSyntaxKind::Arrow, pos()); if (!funNode) return null(); @@ -8609,6 +9284,17 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl } else if (handler.isPropertyAccess(lhs)) { // Permitted: no additional testing/fixup needed. } else if (handler.isFunctionCall(lhs)) { + // We don't have to worry about backward compatibility issues with the new + // compound assignment operators, so we always throw here. Also that way we + // don't have to worry if |f() &&= expr| should always throw an error or + // only if |f()| returns true. + if (kind == PNK_COALESCEASSIGN || + kind == PNK_ORASSIGN || + kind == PNK_ADDASSIGN) { + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); + return null(); + } + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) return null(); @@ -8932,7 +9618,7 @@ template typename ParseHandler::Node Parser::generatorComprehensionLambda(unsigned begin) { - FunctionNodeType genfn = handler.newFunction(FunctionSyntaxKind::Expression); + FunctionNodeType genfn = handler.newFunction(FunctionSyntaxKind::Expression, pos()); if (!genfn) return null(); @@ -9477,6 +10163,9 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling nextMember = handler.newSetThis(thisName, nextMember); if (!nextMember) return null(); + + if (!noteUsedName(context->names().dotInitializers)) + return null(); } else { nextMember = memberCall(tt, lhs, yieldHandling, possibleError); if (!nextMember) @@ -9677,75 +10366,85 @@ Parser::memberCall( template bool -Parser::checkLabelOrIdentifierReference(HandlePropertyName ident, +Parser::checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling) + YieldHandling yieldHandling, + TokenKind hint /* = TOK_LIMIT */) { - if (ident == context->names().yield) { - if (yieldHandling == YieldIsKeyword || - versionNumber() >= JSVERSION_1_7) - { - errorAt(offset, JSMSG_RESERVED_ID, "yield"); - return false; + TokenKind tt; + if (hint == TOK_LIMIT) { + tt = ReservedWordTokenKind(ident); + } else { + MOZ_ASSERT(hint == ReservedWordTokenKind(ident), "hint doesn't match actual token kind"); + tt = hint; + } + + if (!pc->sc()->allowArguments() && ident == context->names().arguments) { + error(JSMSG_BAD_ARGUMENTS); + return false; + } + + if (tt == TOK_NAME || tt == TOK_PRIVATE_NAME) + return true; + if (TokenKindIsContextualKeyword(tt)) { + if (tt == TOK_YIELD) { + if (yieldHandling == YieldIsKeyword || versionNumber() >= JSVERSION_1_7) { + errorAt(offset, JSMSG_RESERVED_ID, "yield"); + return false; + } + if (pc->sc()->needStrictChecks()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) + return false; + } + return true; + } + if (tt == TOK_AWAIT) { + if (awaitIsKeyword() || awaitIsDisallowed()) { + errorAt(offset, JSMSG_RESERVED_ID, "await"); + return false; + } + return true; } if (pc->sc()->needStrictChecks()) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) - return false; - } - - return true; - } - - if (ident == context->names().await) { - if (awaitIsKeyword()) { - errorAt(offset, JSMSG_RESERVED_ID, "await"); - return false; + if (tt == TOK_LET) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) + return false; + return true; + } + if (tt == TOK_STATIC) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) + return false; + return true; + } } return true; } - - if (IsKeyword(ident) || IsReservedWordLiteral(ident)) { - errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(ident)); + if (TokenKindIsStrictReservedWord(tt)) { + if (pc->sc()->needStrictChecks()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(tt))) + return false; + } + return true; + } + if (TokenKindIsKeyword(tt) || TokenKindIsReservedWordLiteral(tt)) { + errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(tt)); return false; } - - if (IsFutureReservedWord(ident)) { - errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(ident)); + if (TokenKindIsFutureReservedWord(tt)) { + errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(tt)); return false; } - - if (pc->sc()->needStrictChecks()) { - if (IsStrictReservedWord(ident)) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(ident))) - return false; - return true; - } - - if (ident == context->names().let) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) - return false; - return true; - } - - if (ident == context->names().static_) { - if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) - return false; - return true; - } - } - - return true; + MOZ_ASSERT_UNREACHABLE("Unexpected reserved word kind."); + return false; } template bool -Parser::checkBindingIdentifier(HandlePropertyName ident, +Parser::checkBindingIdentifier(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling) + YieldHandling yieldHandling, + TokenKind hint /* = TOK_LIMIT */) { - if (!checkLabelOrIdentifierReference(ident, offset, yieldHandling)) - return false; - if (pc->sc()->needStrictChecks()) { if (ident == context->names().arguments) { if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) @@ -9760,7 +10459,7 @@ Parser::checkBindingIdentifier(HandlePropertyName ident, } } - return true; + return checkLabelOrIdentifierReference(ident, offset, yieldHandling, hint); } template @@ -9774,8 +10473,13 @@ Parser::labelOrIdentifierReference(YieldHandling yieldHandling) // // Use PropertyName* instead of TokenKind to reflect the normalization. + // Unless the name contains escapes, we can reuse the current TokenKind + // to determine if the name is a restricted identifier. + TokenKind hint = !tokenStream.currentNameHasEscapes() + ? tokenStream.currentToken().type + : TOK_LIMIT; RootedPropertyName ident(context, tokenStream.currentName()); - if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling)) + if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling, hint)) return nullptr; return ident; } @@ -9784,8 +10488,11 @@ template PropertyName* Parser::bindingIdentifier(YieldHandling yieldHandling) { + TokenKind hint = !tokenStream.currentNameHasEscapes() + ? tokenStream.currentToken().type + : TOK_LIMIT; RootedPropertyName ident(context, tokenStream.currentName()); - if (!checkBindingIdentifier(ident, pos().begin, yieldHandling)) + if (!checkBindingIdentifier(ident, pos().begin, yieldHandling, hint)) return nullptr; return ident; } @@ -10047,41 +10754,119 @@ DoubleToAtom(ExclusiveContext* cx, double value) template typename ParseHandler::Node Parser::propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const Maybe& maybeDecl, ListNodeType propList, + MutableHandleAtom propAtom) +{ + // PropertyName[Yield, Await]: + // LiteralPropertyName + // ComputedPropertyName[?Yield, ?Await] + // + // LiteralPropertyName: + // IdentifierName + // StringLiteral + // NumericLiteral + TokenKind ltok = tokenStream.currentToken().type; + + propAtom.set(nullptr); + switch (ltok) { + case TOK_NUMBER: + propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); + if (!propAtom.get()) + return null(); + return newNumber(tokenStream.currentToken()); + + case TOK_STRING: { + propAtom.set(tokenStream.currentToken().atom()); + uint32_t index; + if (propAtom->isIndex(&index)) { + return handler.newNumber(index, NoDecimal, pos()); + } + return stringLiteral(); + } + + case TOK_LB: + return computedPropertyName(yieldHandling, maybeDecl, propList); + + default: { + if (!TokenKindIsPossibleIdentifierName(ltok)) { + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); + return null(); + } + + propAtom.set(tokenStream.currentName()); + return handler.newObjectLiteralPropertyName(propAtom, pos()); + } + } +} + +// True if `kind` can be the first token of a PropertyName. +static bool +TokenKindCanStartPropertyName(TokenKind tt) +{ + return TokenKindIsPossibleIdentifierName(tt) || tt == TOK_STRING || + tt == TOK_NUMBER || tt == TOK_LB || + tt == TOK_MUL; +} + +template +typename ParseHandler::Node +Parser::propertyOrMethodName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, const Maybe& maybeDecl, ListNodeType propList, PropertyType* propType, MutableHandleAtom propAtom) { + // We're parsing an object literal, class, or destructuring pattern; + // propertyNameContext tells which one. This method parses any of the + // following, storing the corresponding PropertyType in `*propType` to tell + // the caller what we parsed: + // + // async [no LineTerminator here] PropertyName + // ==> PropertyType::AsyncMethod + // async [no LineTerminator here] * PropertyName + // ==> PropertyType::AsyncGeneratorMethod + // * PropertyName ==> PropertyType::GeneratorMethod + // get PropertyName ==> PropertyType::Getter + // set PropertyName ==> PropertyType::Setter + // PropertyName : ==> PropertyType::Normal + // PropertyName ==> see below + // + // In the last case, where there's not a `:` token to consume, we peek at + // (but don't consume) the next token to decide how to set `*propType`. + // + // `,` or `}` ==> PropertyType::Shorthand + // `(` ==> PropertyType::Method + // `=`, not in a class ==> PropertyType::CoverInitializedName + // '=', in a class ==> PropertyType::Field + // any token, in a class ==> PropertyType::Field (ASI) + // + // The caller must check `*propType` and throw if whatever we parsed isn't + // allowed here (for example, a getter in a destructuring pattern). + // + // This method does *not* match `static` (allowed in classes) or `...` + // (allowed in object literals and patterns). The caller must take care of + // those before calling this method. + TokenKind ltok; if (!tokenStream.getToken(<ok)) return null(); MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC"); + // Accept `async` and/or `*`, indicating an async or generator method; + // or `get` or `set`, indicating an accessor. bool isGenerator = false; bool isAsync = false; + bool isGetter = false; + bool isSetter = false; if (ltok == TOK_ASYNC) { - // AsyncMethod[Yield, Await]: - // async [no LineTerminator here] PropertyName[?Yield, ?Await] ... - // - // AsyncGeneratorMethod[Yield, Await]: - // async [no LineTerminator here] * PropertyName[?Yield, ?Await] ... - // - // PropertyName: - // LiteralPropertyName - // ComputedPropertyName[?Yield, ?Await] - // - // LiteralPropertyName: - // IdentifierName - // StringLiteral - // NumericLiteral - // - // ComputedPropertyName[Yield, Await]: - // [ ... + // `async` is also a PropertyName by itself (it's a conditional keyword), + // so peek at the next token to see if we're really looking at a method. TokenKind tt = TOK_EOF; if (!tokenStream.peekTokenSameLine(&tt)) return null(); - if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB || - TokenKindIsPossibleIdentifierName(tt) || tt == TOK_MUL) + if (TokenKindCanStartPropertyName(tt)) { isAsync = true; tokenStream.consumeKnownToken(tt); @@ -10095,109 +10880,33 @@ Parser::propertyName(YieldHandling yieldHandling, return null(); } - propAtom.set(nullptr); - Node propName; - switch (ltok) { - case TOK_NUMBER: - propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); - if (!propAtom.get()) - return null(); - propName = newNumber(tokenStream.currentToken()); - if (!propName) - return null(); - break; - - case TOK_STRING: { - propAtom.set(tokenStream.currentToken().atom()); - uint32_t index; - if (propAtom->isIndex(&index)) { - propName = handler.newNumber(index, NoDecimal, pos()); - if (!propName) - return null(); - break; - } - propName = stringLiteral(); - if (!propName) - return null(); - break; - } - - case TOK_LB: - propName = computedPropertyName(yieldHandling, maybeDecl, propList); - if (!propName) - return null(); - break; - - default: { - if (!TokenKindIsPossibleIdentifierName(ltok)) { - error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); - return null(); - } - - propAtom.set(tokenStream.currentName()); - // Do not look for accessor syntax on generator or async methods. - if (isGenerator || isAsync || !(ltok == TOK_GET || ltok == TOK_SET)) { - propName = handler.newObjectLiteralPropertyName(propAtom, pos()); - if (!propName) - return null(); - break; - } - - *propType = ltok == TOK_GET ? PropertyType::Getter : PropertyType::Setter; - + if (!isAsync && !isGenerator && + (ltok == TOK_GET || ltok == TOK_SET)) { // We have parsed |get| or |set|. Look for an accessor property // name next. TokenKind tt; if (!tokenStream.peekToken(&tt)) return null(); - if (TokenKindIsPossibleIdentifierName(tt)) { + if (TokenKindCanStartPropertyName(tt)) { tokenStream.consumeKnownToken(tt); - - propAtom.set(tokenStream.currentName()); - return handler.newObjectLiteralPropertyName(propAtom, pos()); + isGetter = (ltok == TOK_GET); + isSetter = (ltok == TOK_SET); } - if (tt == TOK_STRING) { - tokenStream.consumeKnownToken(TOK_STRING); - - propAtom.set(tokenStream.currentToken().atom()); - - uint32_t index; - if (propAtom->isIndex(&index)) { - propAtom.set(DoubleToAtom(context, index)); - if (!propAtom.get()) - return null(); - return handler.newNumber(index, NoDecimal, pos()); - } - return stringLiteral(); - } - if (tt == TOK_NUMBER) { - tokenStream.consumeKnownToken(TOK_NUMBER); - - propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number())); - if (!propAtom.get()) - return null(); - return newNumber(tokenStream.currentToken()); - } - if (tt == TOK_LB) { - tokenStream.consumeKnownToken(TOK_LB); - - return computedPropertyName(yieldHandling, maybeDecl, propList); - } - - // Not an accessor property after all. - propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos()); - if (!propName) - return null(); - break; - } } + Node propName = propertyName(yieldHandling, propertyNameContext, maybeDecl, + propList, propAtom); + if (!propName) + return null(); + + // Grab the next token following the property/method name. + // (If this isn't a colon, we're going to either put it back or throw.) TokenKind tt; if (!tokenStream.getToken(&tt)) return null(); if (tt == TOK_COLON) { - if (isGenerator || isAsync) { + if (isGenerator || isAsync || isGetter || isSetter) { error(JSMSG_BAD_PROP_ID); return null(); } @@ -10205,10 +10914,11 @@ Parser::propertyName(YieldHandling yieldHandling, return propName; } - if (TokenKindIsPossibleIdentifierName(ltok) && + if (propertyNameContext != PropertyNameInClass && + TokenKindIsPossibleIdentifierName(ltok) && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) { - if (isGenerator || isAsync) { + if (isGenerator || isAsync || isGetter || isSetter) { error(JSMSG_BAD_PROP_ID); return null(); } @@ -10227,11 +10937,25 @@ Parser::propertyName(YieldHandling yieldHandling, *propType = PropertyType::GeneratorMethod; else if (isAsync) *propType = PropertyType::AsyncMethod; + else if (isGetter) + *propType = PropertyType::Getter; + else if (isSetter) + *propType = PropertyType::Setter; else *propType = PropertyType::Method; return propName; } + if (propertyNameContext == PropertyNameInClass) { + if (isGenerator || isAsync || isGetter || isSetter) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + tokenStream.ungetToken(); + *propType = PropertyType::Field; + return propName; + } + error(JSMSG_COLON_AFTER_ID); return null(); } @@ -10305,7 +11029,8 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* TokenPos namePos = tokenStream.nextToken().pos; PropertyType propType; - Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInLiteral, declKind, + literal, &propType, &propAtom); if (!propName) return null(); @@ -10536,7 +11261,7 @@ Parser::methodDefinition(uint32_t toStringStart, PropertyType prop YieldHandling yieldHandling = GetYieldHandling(generatorKind); - FunctionNodeType funNode = handler.newFunction(syntaxKind); + FunctionNodeType funNode = handler.newFunction(syntaxKind, pos()); if (!funNode) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 4dd9f64178..6976487c50 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -526,6 +526,10 @@ class ParseContext : public Nestable return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isMethod(); } + bool allowReturn() const { + return sc_->isFunctionBox() && sc_->asFunctionBox()->allowReturn(); + } + uint32_t scriptId() const { return scriptId_; } @@ -583,15 +587,16 @@ enum class PropertyType { AsyncMethod, AsyncGeneratorMethod, Constructor, - DerivedConstructor + DerivedConstructor, + Field, }; // Specify a value for an ES6 grammar parametrization. We have no enum for -// [Return] because its behavior is exactly equivalent to checking whether +// [Return] because its behavior is almost exactly equivalent to checking whether // we're in a function box -- easier and simpler than passing an extra // parameter everywhere. enum YieldHandling { YieldIsName, YieldIsKeyword }; -enum AwaitHandling : uint8_t { AwaitIsName, AwaitIsKeyword, AwaitIsModuleKeyword }; +enum AwaitHandling : uint8_t { AwaitIsName, AwaitIsKeyword, AwaitIsModuleKeyword, AwaitIsDisallowed }; enum InHandling { InAllowed, InProhibited }; enum DefaultHandling { NameRequired, AllowDefaultName }; enum TripledotHandling { TripledotAllowed, TripledotProhibited }; @@ -720,6 +725,15 @@ class UsedNameTracker MOZ_MUST_USE bool noteUse(ExclusiveContext* cx, JSAtom* name, uint32_t scriptId, uint32_t scopeId); + MOZ_MUST_USE bool markAsAlwaysClosedOver(ExclusiveContext* cx, JSAtom* name, + uint32_t scriptId, uint32_t scopeId) { + // This marks a variable as always closed over: + // UsedNameInfo::noteBoundInScope only checks if scriptId and scopeId are + // greater than the current scriptId/scopeId, so do a simple increment to + // make that so. + return noteUse(cx, name, scriptId + 1, scopeId + 1); + } + struct RewindToken { private: @@ -807,7 +821,10 @@ class ParserBase : public StrictModeGetter public: bool awaitIsKeyword() const { - return awaitHandling_ != AwaitIsName; + return awaitHandling_ == AwaitIsKeyword || awaitHandling_ == AwaitIsModuleKeyword; + } + bool awaitIsDisallowed() const { + return awaitHandling_ == AwaitIsDisallowed; } ParseGoal parseGoal() const { @@ -1171,7 +1188,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) */ JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - HandleObject proto); + HandleObject proto = nullptr); void trace(JSTracer* trc); @@ -1440,6 +1457,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) // Parse a function body. Pass StatementListBody if the body is a list of // statements; pass ExpressionBody if the body is a single expression. + // + // Don't include opening LeftCurly token when invoking. enum FunctionBodyType { StatementListBody, ExpressionBody }; LexicalScopeNodeType functionBody(InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, FunctionBodyType type); @@ -1478,18 +1497,53 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) enum ClassContext { ClassStatement, ClassExpression }; ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); + struct ClassFields { + // The number of instance class fields. + size_t instanceFields = 0; - bool checkLabelOrIdentifierReference(HandlePropertyName ident, + // The number of instance class fields with computed property names. + size_t instanceFieldKeys = 0; + + // The number of static class fields. + size_t staticFields = 0; + + // The number of static blocks + size_t staticBlocks = 0; + + // The number of static class fields with computed property names. + size_t staticFieldKeys = 0; + }; + MOZ_MUST_USE bool classMember(YieldHandling yieldHandling, + const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, + uint32_t classStartOffset, bool hasHeritage, + ClassFields& classFields, + ListNodeType& classMembers, bool* done); + MOZ_MUST_USE bool finishClassConstructor( + const ParseContext::ClassStatement& classStmt, + HandlePropertyName className, bool hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + const ClassFields& classFields, ListNodeType& classMembers); + + FunctionNodeType fieldInitializerOpt(HandleAtom atom, ClassFields& classFields, bool isStatic); + FunctionNodeType staticClassBlock(ClassFields& classFields); + FunctionNodeType synthesizeConstructor(HandleAtom className, + uint32_t classNameOffset, + bool hasHeritage); + + bool checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling); + YieldHandling yieldHandling, + TokenKind hint = TOK_LIMIT); - bool checkLocalExportName(HandlePropertyName ident, uint32_t offset) { + bool checkLocalExportName(PropertyName* ident, uint32_t offset) { return checkLabelOrIdentifierReference(ident, offset, YieldIsName); } - bool checkBindingIdentifier(HandlePropertyName ident, + bool checkBindingIdentifier(PropertyName* ident, uint32_t offset, - YieldHandling yieldHandling); + YieldHandling yieldHandling, + TokenKind hint = TOK_LIMIT); PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling); @@ -1520,8 +1574,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) bool matchInOrOf(bool* isForInp, bool* isForOfp); bool hasUsedFunctionSpecialName(HandlePropertyName name); - bool declareFunctionArgumentsObject(); - bool declareFunctionThis(); + bool declareFunctionArgumentsObject(bool canSkipLazyClosedOverBindings); + bool declareFunctionThis(bool canSkipLazyClosedOverBindings); NameNodeType newInternalDotName(HandlePropertyName name); NameNodeType newThisName(); NameNodeType newDotGeneratorName(); @@ -1594,9 +1648,15 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) mozilla::Maybe newLexicalScopeData(ParseContext::Scope& scope); LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body); + enum PropertyNameContext { PropertyNameInLiteral, PropertyNameInPattern, PropertyNameInClass }; Node propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, const mozilla::Maybe& maybeDecl, ListNodeType propList, - PropertyType* propType, MutableHandleAtom propAtom); + MutableHandleAtom propAtom); + Node propertyOrMethodName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const mozilla::Maybe& maybeDecl, ListNodeType propList, + PropertyType* propType, MutableHandleAtom propAtom); UnaryNodeType computedPropertyName(YieldHandling yieldHandling, const mozilla::Maybe& maybeDecl, ListNodeType literal); ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError); diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp index fd00024e7c..7be5f17230 100644 --- a/js/src/frontend/PropOpEmitter.cpp +++ b/js/src/frontend/PropOpEmitter.cpp @@ -110,11 +110,11 @@ PropOpEmitter::emitGet(JSAtom* prop) bool PropOpEmitter::prepareForRhs() { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); - MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj); + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Obj); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); - if (isSimpleAssignment()) { + if (isSimpleAssignment() || isPropInit()) { // For CompoundAssignment, SUPERBASE is already emitted by emitGet. if (isSuper()) { if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS SUPERBASE @@ -133,7 +133,7 @@ bool PropOpEmitter::skipObjAndRhs() { MOZ_ASSERT(state_ == State::Start); - MOZ_ASSERT(isSimpleAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()); #ifdef DEBUG state_ = State::Rhs; @@ -182,18 +182,20 @@ PropOpEmitter::emitDelete(JSAtom* prop) bool PropOpEmitter::emitAssignment(JSAtom* prop) { - MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment()); + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT(state_ == State::Rhs); - if (isSimpleAssignment()) { + if (isSimpleAssignment() || isPropInit()) { if (!prepareAtomIndex(prop)) { return false; } } - JSOp setOp = isSuper() - ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER - : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; + MOZ_ASSERT_IF(isPropInit(), !isSuper()); + JSOp setOp = isPropInit() ? JSOP_INITPROP + : isSuper() + ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER + : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; if (!bce_->emitAtomOp(propAtomIndex_, setOp)) { // VAL return false; } diff --git a/js/src/frontend/PropOpEmitter.h b/js/src/frontend/PropOpEmitter.h index 2c5a26ec45..2e6d6e5089 100644 --- a/js/src/frontend/PropOpEmitter.h +++ b/js/src/frontend/PropOpEmitter.h @@ -115,6 +115,7 @@ class MOZ_STACK_CLASS PropOpEmitter PostDecrement, PreDecrement, SimpleAssignment, + PropInit, CompoundAssignment }; enum class ObjKind { @@ -167,6 +168,7 @@ class MOZ_STACK_CLASS PropOpEmitter // | +--------+ | // | | // | [SimpleAssignment] | + // | [PropInit] | // | prepareForRhs | +-----+ // +--------------------->+-------------->+->| Rhs |-+ // | ^ +-----+ | @@ -217,6 +219,10 @@ class MOZ_STACK_CLASS PropOpEmitter return kind_ == Kind::SimpleAssignment; } + MOZ_MUST_USE bool isPropInit() const { + return kind_ == Kind::PropInit; + } + MOZ_MUST_USE bool isDelete() const { return kind_ == Kind::Delete; } diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 81eb0885b0..29a8cbd18f 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -155,6 +155,7 @@ class FunctionContextFlags bool needsHomeObject:1; bool isDerivedClassConstructor:1; + bool isFieldInitializer:1; // Whether this function has a .this binding. If true, we need to emit // JSOP_FUNCTIONTHIS in the prologue to initialize it. @@ -170,6 +171,7 @@ class FunctionContextFlags definitelyNeedsArgsObj(false), needsHomeObject(false), isDerivedClassConstructor(false), + isFieldInitializer(false), hasThisBinding(false), hasInnerFunctions(false) { } @@ -243,6 +245,7 @@ class SharedContext bool allowNewTarget_; bool allowSuperProperty_; bool allowSuperCall_; + bool allowArguments_; bool inWith_; bool needsThisTDZChecks_; @@ -262,6 +265,7 @@ class SharedContext allowNewTarget_(false), allowSuperProperty_(false), allowSuperCall_(false), + allowArguments_(true), inWith_(false), needsThisTDZChecks_(false) { } @@ -286,6 +290,7 @@ class SharedContext bool allowNewTarget() const { return allowNewTarget_; } bool allowSuperProperty() const { return allowSuperProperty_; } bool allowSuperCall() const { return allowSuperCall_; } + bool allowArguments() const { return allowArguments_; } bool inWith() const { return inWith_; } bool needsThisTDZChecks() const { return needsThisTDZChecks_; } @@ -427,6 +432,8 @@ class FunctionBox : public ObjectBox, public SharedContext bool isExprBody_:1; /* arrow function with expression * body or expression closure: * function(x) x*x */ + bool allowReturn_ : 1; /* Used to issue an early error in static class blocks. */ + FunctionContextFlags funCxFlags; @@ -451,7 +458,7 @@ class FunctionBox : public ObjectBox, public SharedContext void initFromLazyFunction(); void initStandaloneFunction(Scope* enclosingScope); - void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); + void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); ObjectBox* toObjectBox() override { return this; } JSFunction* function() const { return &object->as(); } @@ -518,6 +525,8 @@ class FunctionBox : public ObjectBox, public SharedContext isExprBody_ = true; } + bool allowReturn() const { return allowReturn_; } + void setGeneratorKind(GeneratorKind kind) { // A generator kind can be set at initialization, or when "yield" is // first seen. In both cases the transition can only happen from @@ -533,6 +542,7 @@ class FunctionBox : public ObjectBox, public SharedContext bool needsHomeObject() const { return funCxFlags.needsHomeObject; } bool isDerivedClassConstructor() const { return funCxFlags.isDerivedClassConstructor; } bool hasInnerFunctions() const { return funCxFlags.hasInnerFunctions; } + bool isFieldInitializer() const { return funCxFlags.isFieldInitializer; } void setHasExtensibleScope() { funCxFlags.hasExtensibleScope = true; } void setHasThisBinding() { funCxFlags.hasThisBinding = true; } @@ -544,6 +554,8 @@ class FunctionBox : public ObjectBox, public SharedContext void setDerivedClassConstructor() { MOZ_ASSERT(function()->isClassConstructor()); funCxFlags.isDerivedClassConstructor = true; } void setHasInnerFunctions() { funCxFlags.hasInnerFunctions = true; } + void setFieldInitializer() { MOZ_ASSERT(function()->isMethod()); + funCxFlags.isFieldInitializer = true; } bool hasSimpleParameterList() const { return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; @@ -563,7 +575,11 @@ class FunctionBox : public ObjectBox, public SharedContext } void setStart(const TokenStream& tokenStream) { - bufStart = tokenStream.currentToken().pos.begin; + setStart(tokenStream, tokenStream.currentToken().pos); + } + + void setStart(const TokenStream& tokenStream, const TokenPos& tokenPos) { + bufStart = tokenPos.begin; tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn); } diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index d8bc3e4959..130a5da61d 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -312,7 +312,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) Node newGenExp(Node callee, Node args) { return NodeGeneric; } ListNodeType newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } - ListNodeType newClassMethodList(uint32_t begin) { return NodeGeneric; } + ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; } ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; } ClassNodeType newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; } @@ -331,7 +331,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, NameNodeType expr) { return true; } MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, Node inner) { return true; } MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op) { return true; } - MOZ_MUST_USE bool addClassMethodDefinition(ListNodeType literal, Node name, FunctionNodeType funNode, JSOp op, bool isStatic) { return true; } + MOZ_MUST_USE Node newClassMethodDefinition(Node key, FunctionNodeType funNode, JSOp op, bool isStatic) { return NodeGeneric; } + MOZ_MUST_USE Node newClassFieldDefinition(Node name, FunctionNodeType initializer, bool isStatic) { return NodeGeneric; } + MOZ_MUST_USE Node newStaticClassBlock(FunctionNodeType block) { return NodeGeneric; } + MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, Node member) { return true; } UnaryNodeType newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; } @@ -417,7 +420,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) void checkAndSetIsDirectRHSAnonFunction(Node pn) {} - FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind) { return NodeFunctionDefinition; } + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, const TokenPos& pos) { return NodeFunctionDefinition; } bool setComprehensionLambdaBody(FunctionNodeType funNode, ListNodeType body) { return true; } void setFunctionFormalParametersAndBody(FunctionNodeType funNode, ListNodeType paramsBody) {} @@ -468,7 +471,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) MOZ_ASSERT(kind != PNK_CONST); return NodeGeneric; } - ListNodeType newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) { + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos, JSOp op = JSOP_NOP) { return newList(kind, op); } ListNodeType newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) { diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 745e1b6987..232e373dcf 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -71,6 +71,7 @@ macro(LP, "'('") \ macro(RP, "')'") \ macro(NAME, "identifier") \ + macro(PRIVATE_NAME, "private identifier") \ macro(NUMBER, "numeric literal") \ macro(STRING, "string literal") \ \ @@ -218,6 +219,9 @@ range(ASSIGNMENT_START, ASSIGN) \ macro(ADDASSIGN, "'+='") \ macro(SUBASSIGN, "'-='") \ + macro(COALESCEASSIGN, "'\?\?='") /* avoid trigraphs warning */ \ + macro(ORASSIGN, "'||='") \ + macro(ANDASSIGN, "'&&='") \ macro(BITORASSIGN, "'|='") \ macro(BITXORASSIGN, "'^='") \ macro(BITANDASSIGN, "'&='") \ @@ -322,6 +326,7 @@ inline MOZ_MUST_USE bool TokenKindIsPossibleIdentifier(TokenKind tt) { return tt == TOK_NAME || + tt == TOK_PRIVATE_NAME || TokenKindIsContextualKeyword(tt) || TokenKindIsStrictReservedWord(tt); } diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 2539249ad9..c58bc4bd47 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -93,18 +93,35 @@ FindReservedWord(const CharT* s, size_t length) } static const ReservedWordInfo* -FindReservedWord(JSLinearString* str) +FindReservedWord(JSLinearString* str, js::frontend::NameVisibility* visibility) { JS::AutoCheckCannotGC nogc; - return str->hasLatin1Chars() - ? FindReservedWord(str->latin1Chars(nogc), str->length()) - : FindReservedWord(str->twoByteChars(nogc), str->length()); + if (str->hasLatin1Chars()) { + const JS::Latin1Char* chars = str->latin1Chars(nogc); + size_t length = str->length(); + if (length > 0 && chars[0] == '#') { + *visibility = js::frontend::NameVisibility::Private; + return nullptr; + } + *visibility = js::frontend::NameVisibility::Public; + return FindReservedWord(chars, length); + } + + const char16_t* chars = str->twoByteChars(nogc); + size_t length = str->length(); + if (length > 0 && chars[0] == '#') { + *visibility = js::frontend::NameVisibility::Private; + return nullptr; + } + *visibility = js::frontend::NameVisibility::Public; + return FindReservedWord(chars, length); } template static bool IsIdentifier(const CharT* chars, size_t length) { + // Generic version for latin1 in char* and UCS-2 in char16_t* if (length == 0) return false; @@ -138,14 +155,52 @@ GetSingleCodePoint(const char16_t** p, const char16_t* end) return codePoint; } +namespace js { + +namespace frontend { + +// Latin1 Variants + +bool +IsIdentifier(const Latin1Char* chars, size_t length) +{ + return ::IsIdentifier(chars, length); +} + +static bool +IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length) +{ + if (length == 0) + return false; + + if (char16_t(*chars) == '#') { + ++chars; + --length; + } + + return IsIdentifier(chars, length); +} + +// UTF-16 Versions + +bool +IsIdentifier(const char16_t* chars, size_t length) +{ + return ::IsIdentifier(chars, length); +} + static bool IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) { - if (IsIdentifier(chars, length)) - return true; - if (length == 0) return false; + // XXX Revisit if this is still faster. + // Assumption is that iterating the string twice in the rare worst case (not a valid UCS-2 + // identifier, but valid in UTF-16) is on average better than parsing UTF-16 code points + // individually for every input. + if (IsIdentifier(chars, length)) { + return true; + } const char16_t* p = chars; const char16_t* end = chars + length; @@ -164,71 +219,78 @@ IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) return true; } +static bool +IsIdentifierNameOrPrivateNameMaybeNonBMP(const char16_t* chars, size_t length) +{ + if (length == 0) + return false; + + // '#' is always just one character in either UCS-2 or UTF-16, so compare it directly. + if (char16_t(*chars) == '#') { + ++chars; + --length; + } + + return IsIdentifierMaybeNonBMP(chars, length); +} + bool -frontend::IsIdentifier(JSLinearString* str) +IsIdentifier(JSLinearString* str) { JS::AutoCheckCannotGC nogc; - return str->hasLatin1Chars() - ? ::IsIdentifier(str->latin1Chars(nogc), str->length()) - : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); + if (str->hasLatin1Chars()) { + return IsIdentifier(str->latin1Chars(nogc), str->length()); + + } + return IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool -frontend::IsIdentifier(const char* chars, size_t length) +IsIdentifierNameOrPrivateName(JSLinearString* str) { - return ::IsIdentifier(chars, length); + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length()); + + } + return IsIdentifierNameOrPrivateNameMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool -frontend::IsIdentifier(const char16_t* chars, size_t length) +IsKeyword(JSLinearString* str) { - return ::IsIdentifier(chars, length); -} - -bool -frontend::IsKeyword(JSLinearString* str) -{ - if (const ReservedWordInfo* rw = FindReservedWord(str)) + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) return TokenKindIsKeyword(rw->tokentype); return false; } -bool -frontend::IsFutureReservedWord(JSLinearString* str) +TokenKind +ReservedWordTokenKind(PropertyName* str) { - if (const ReservedWordInfo* rw = FindReservedWord(str)) - return TokenKindIsFutureReservedWord(rw->tokentype); + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) + return rw->tokentype; - return false; -} - -bool -frontend::IsStrictReservedWord(JSLinearString* str) -{ - if (const ReservedWordInfo* rw = FindReservedWord(str)) - return TokenKindIsStrictReservedWord(rw->tokentype); - - return false; -} - -bool -frontend::IsReservedWordLiteral(JSLinearString* str) -{ - if (const ReservedWordInfo* rw = FindReservedWord(str)) - return TokenKindIsReservedWordLiteral(rw->tokentype); - - return false; + return visibility == NameVisibility::Private ? TOK_PRIVATE_NAME : TOK_NAME; } const char* -frontend::ReservedWordToCharZ(PropertyName* str) +ReservedWordToCharZ(PropertyName* str) { - const ReservedWordInfo* rw = FindReservedWord(str); - if (rw == nullptr) - return nullptr; + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) + return ReservedWordToCharZ(rw->tokentype); - switch (rw->tokentype) { + return nullptr; +} + +const char* +ReservedWordToCharZ(TokenKind tt) +{ + MOZ_ASSERT(tt != TOK_NAME); + switch (tt) { #define EMIT_CASE(word, name, type) case type: return js_##word##_str; FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) #undef EMIT_CASE @@ -238,6 +300,10 @@ frontend::ReservedWordToCharZ(PropertyName* str) return nullptr; } +} // namespace frontend + +} // namespace js + PropertyName* TokenStream::reservedWordToPropertyName(TokenKind tt) const { @@ -604,7 +670,7 @@ TokenStream::TokenBuf::findEOLMax(size_t start, size_t max) if (n >= max) break; n++; - + // This stops at U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR in // string and template literals. These code points do affect line and // column coordinates, even as they encode their literal values. @@ -1314,6 +1380,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) bool hasExp; DecimalPoint decimalPoint; const char16_t* identStart; + NameVisibility identVisibility; bool hadUnicodeEscape; // Check if in the middle of a template string. Have to get this out of @@ -1358,6 +1425,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (unicode::IsUnicodeIDStart(char16_t(c))) { identStart = userbuf.addressOfNextRawChar() - 1; hadUnicodeEscape = false; + identVisibility = NameVisibility::Public; goto identifier; } @@ -1368,6 +1436,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) { identStart = userbuf.addressOfNextRawChar() - 2; hadUnicodeEscape = false; + identVisibility = NameVisibility::Public; goto identifier; } } @@ -1416,6 +1485,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) tp = newToken(-1); identStart = userbuf.addressOfNextRawChar() - 1; hadUnicodeEscape = false; + identVisibility = NameVisibility::Public; identifier: for (;;) { @@ -1457,11 +1527,14 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) length = userbuf.addressOfNextRawChar() - identStart; } - // Represent reserved words as reserved word tokens. - if (!hadUnicodeEscape) { - if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { - tp->type = rw->tokentype; - goto out; + // Private identifiers start with a '#', and so cannot be reserved words. + if (identVisibility == NameVisibility::Public) { + // Represent reserved words as reserved word tokens. + if (!hadUnicodeEscape) { + if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { + tp->type = rw->tokentype; + goto out; + } } } @@ -1469,7 +1542,12 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (!atom) { goto error; } - tp->type = TOK_NAME; + if (identVisibility == NameVisibility::Private) { + MOZ_ASSERT(identStart[0] == '#', "Private identifier starts with #"); + tp->type = TOK_PRIVATE_NAME; + } else { + tp->type = TOK_NAME; + } tp->setName(atom->asPropertyName()); goto out; } @@ -1774,14 +1852,31 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) if (escapeLength > 0) { identStart = userbuf.addressOfNextRawChar() - escapeLength - 1; hadUnicodeEscape = true; + identVisibility = NameVisibility::Public; goto identifier; } goto badchar; } + case '#': { + // TODO: This does not handle escaped private property names due to being extremely difficult + // in the current state of the tokenizer. If #1351107 is ported, it becomes straightforward. + c = getCharIgnoreEOL(); + // '$' and '_' are not in IsUnicodeIDStart + c1kind = FirstCharKind(firstCharKinds[c]); + if (c1kind == Ident || unicode::IsUnicodeIDStart(char16_t(c))) { + identStart = userbuf.addressOfNextRawChar() - 2; + hadUnicodeEscape = false; + identVisibility = NameVisibility::Private; + goto identifier; + } + ungetCharIgnoreEOL(c); + goto badchar; + } + case '|': if (matchChar('|')) - tp->type = TOK_OR; + tp->type = matchChar('=') ? TOK_ORASSIGN : TOK_OR; else tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR; goto out; @@ -1792,7 +1887,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) case '&': if (matchChar('&')) - tp->type = TOK_AND; + tp->type = matchChar('=') ? TOK_ANDASSIGN : TOK_AND; else tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND; goto out; @@ -1811,8 +1906,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) ungetCharIgnoreEOL(c); tp->type = TOK_OPTCHAIN; } - } else { - tp->type = matchChar('?') ? TOK_COALESCE : TOK_HOOK; + } else if (matchChar('?')) { + tp->type = matchChar('=') ? TOK_COALESCEASSIGN : TOK_COALESCE; + } else { + tp->type = TOK_HOOK; } goto out; @@ -2247,7 +2344,7 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp) updateFlagsForEOL(); } else if (c == LINE_SEPARATOR || c == PARA_SEPARATOR) { // U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR encode - // their literal values in template literals and (as of the + // their literal values in template literals and (as of the // JSON superset proposal) string literals, but they still count // as line terminators when computing line/column coordinates. updateLineInfoForEOL(); diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 7129ae6d76..e7a3f7b808 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -92,6 +92,8 @@ enum class InvalidEscapeType { Octal }; +enum class NameVisibility { Public, Private }; + class TokenStream; struct Token @@ -185,7 +187,7 @@ struct Token // Mutators void setName(PropertyName* name) { - MOZ_ASSERT(type == TOK_NAME); + MOZ_ASSERT(type == TOK_NAME || type == TOK_PRIVATE_NAME); u.name = name; } @@ -211,7 +213,7 @@ struct Token // Type-safe accessors PropertyName* name() const { - MOZ_ASSERT(type == TOK_NAME); + MOZ_ASSERT(type == TOK_NAME || type == TOK_PRIVATE_NAME); return u.name->JSAtom::asPropertyName(); // poor-man's type verification } @@ -244,17 +246,14 @@ class CompileError : public JSErrorReport { void throwError(JSContext* cx); }; +extern TokenKind +ReservedWordTokenKind(PropertyName* str); + extern const char* ReservedWordToCharZ(PropertyName* str); -extern MOZ_MUST_USE bool -IsFutureReservedWord(JSLinearString* str); - -extern MOZ_MUST_USE bool -IsReservedWordLiteral(JSLinearString* str); - -extern MOZ_MUST_USE bool -IsStrictReservedWord(JSLinearString* str); +extern const char* +ReservedWordToCharZ(TokenKind tt); // Ideally, tokenizing would be entirely independent of context. But the // strict mode flag, which is in SharedContext, affects tokenizing, and @@ -347,7 +346,7 @@ class MOZ_STACK_CLASS TokenStream public: PropertyName* currentName() const { - if (isCurrentTokenType(TOK_NAME)) { + if (isCurrentTokenType(TOK_NAME) || isCurrentTokenType(TOK_PRIVATE_NAME)) { return currentToken().name(); } @@ -355,6 +354,16 @@ class MOZ_STACK_CLASS TokenStream return reservedWordToPropertyName(currentToken().type); } + bool currentNameHasEscapes() const { + if (isCurrentTokenType(TOK_NAME) || isCurrentTokenType(TOK_PRIVATE_NAME)) { + TokenPos pos = currentToken().pos; + return (pos.end - pos.begin) != currentToken().name()->length(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return false; + } + PropertyName* nextName() const { if (nextToken().type != TOK_NAME) { return nextToken().name(); diff --git a/js/src/js.msg b/js/src/js.msg index 413469b808..91637edc6a 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -216,6 +216,7 @@ MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch state MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'") MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods") MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors") +MSG_DEF(JSMSG_BAD_ARGUMENTS, 0, JSEXN_SYNTAXERR, "arguments is not valid in fields") MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension") MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list") MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression") @@ -264,6 +265,7 @@ MSG_DEF(JSMSG_FROM_AFTER_IMPORT_CLAUSE, 0, JSEXN_SYNTAXERR, "missing keyword 'fr MSG_DEF(JSMSG_FROM_AFTER_EXPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'from' after export *") MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage after {0}, starting with {1}") MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal") +MSG_DEF(JSMSG_MISSING_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "'#' not followed by identifier") MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character") MSG_DEF(JSMSG_IMPORT_META_OUTSIDE_MODULE, 0, JSEXN_SYNTAXERR, "import.meta may only appear in a module") MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level of a module") @@ -345,6 +347,7 @@ MSG_DEF(JSMSG_UNNAMED_CLASS_STMT, 0, JSEXN_SYNTAXERR, "class statement requ MSG_DEF(JSMSG_UNNAMED_FUNCTION_STMT, 0, JSEXN_SYNTAXERR, "function statement requires a name") MSG_DEF(JSMSG_UNTERMINATED_COMMENT, 0, JSEXN_SYNTAXERR, "unterminated comment") MSG_DEF(JSMSG_UNTERMINATED_REGEXP, 0, JSEXN_SYNTAXERR, "unterminated regular expression literal") +MSG_DEF(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK, 0, JSEXN_SYNTAXERR, "unterminated static class block") MSG_DEF(JSMSG_UNTERMINATED_STRING, 0, JSEXN_SYNTAXERR, "unterminated string literal") MSG_DEF(JSMSG_USELESS_EXPR, 0, JSEXN_TYPEERR, "useless expression") MSG_DEF(JSMSG_USE_ASM_DIRECTIVE_FAIL, 0, JSEXN_SYNTAXERR, "\"use asm\" is only meaningful in the Directive Prologue of a function body") @@ -360,6 +363,7 @@ MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allo MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot be used with an optional chain") MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain") MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes") +MSG_DEF(JSMSG_FIELDS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "fields are not currently supported") // asm.js MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index c0bcad81ef..c0fa85bc68 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4429,7 +4429,7 @@ JS::CompileFunction(JSContext* cx, AutoObjectVector& envChain, return false; // If name is not valid identifier - if (!js::frontend::IsIdentifier(name, nameLen)) + if (!js::frontend::IsIdentifier(reinterpret_cast(name), nameLen)) isInvalidName = true; } diff --git a/js/src/jsast.tbl b/js/src/jsast.tbl index 24814c3973..0ef51622b1 100644 --- a/js/src/jsast.tbl +++ b/js/src/jsast.tbl @@ -86,4 +86,6 @@ ASTDEF(AST_COMPUTED_NAME, "ComputedName", "computedNam ASTDEF(AST_CLASS_STMT, "ClassStatement", "classStatement") ASTDEF(AST_CLASS_METHOD, "ClassMethod", "classMethod") +ASTDEF(AST_CLASS_FIELD, "ClassField", "classField") +ASTDEF(AST_STATIC_CLASS_BLOCK, "StaticClassBlock", "staticClassBlock") /* AST_LIMIT = last + 1 */ diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 2f3bd8ca55..67df78c2f1 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1286,6 +1286,19 @@ JSFunction::isDerivedClassConstructor() return derived; } +bool +JSFunction::isFieldInitializer() const +{ + bool init; + if (isInterpretedLazy()) { + init = lazyScript()->isFieldInitializer(); + } else { + init = nonLazyScript()->isFieldInitializer(); + } + MOZ_ASSERT_IF(init, isMethod()); + return init; +} + /* static */ bool JSFunction::getLength(JSContext* cx, HandleFunction fun, uint16_t* length) { diff --git a/js/src/jsfun.h b/js/src/jsfun.h index ad6eb07a2c..1833aaeea5 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -595,6 +595,7 @@ class JSFunction : public js::NativeObject } bool isDerivedClassConstructor(); + bool isFieldInitializer() const; static unsigned offsetOfNativeOrScript() { static_assert(offsetof(U, n.native) == offsetof(U, i.s.script_), diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index ddb33a4de0..a3f5f07068 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -238,6 +238,7 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri uint32_t toStringEnd = script->toStringEnd(); uint32_t lineno = script->lineno(); uint32_t column = script->column(); + uint32_t numFieldInitializers; if (mode == XDR_ENCODE) { packedFields = lazy->packedFields(); @@ -251,10 +252,17 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri // relazify scripts with inner functions. See // JSFunction::createScriptForLazilyInterpretedFunction. MOZ_ASSERT(lazy->numInnerFunctions() == 0); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers; + } else { + numFieldInitializers = UINT32_MAX; + } } if (!xdr->codeUint64(&packedFields)) return false; + if (!xdr->codeUint32(&numFieldInitializers)) + return false; if (mode == XDR_DECODE) { RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap()); @@ -265,6 +273,9 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri return false; lazy->setToStringEnd(toStringEnd); + if (numFieldInitializers != UINT32_MAX) { + lazy->setFieldInitializers(FieldInitializers((size_t)numFieldInitializers)); + } // As opposed to XDRLazyScript, we need to restore the runtime bits // of the script, as we are trying to match the fact this function @@ -339,6 +350,7 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, NeedsHomeObject, IsDerivedClassConstructor, IsDefaultClassConstructor, + IsFieldInitializer, }; uint32_t length, lineno, column, nfixed, nslots; @@ -463,6 +475,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, scriptBits |= (1 << IsDerivedClassConstructor); if (script->isDefaultClassConstructor()) scriptBits |= (1 << IsDefaultClassConstructor); + if (script->isFieldInitializer()) + scriptBits |= (1 << IsFieldInitializer); } if (!xdr->codeUint32(&prologueLength)) @@ -609,6 +623,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, script->isDerivedClassConstructor_ = true; if (scriptBits & (1 << IsDefaultClassConstructor)) script->isDefaultClassConstructor_ = true; + if (scriptBits & (1 << IsFieldInitializer)) + script->isFieldInitializer_ = true; if (scriptBits & (1 << IsLegacyGenerator)) { MOZ_ASSERT(!(scriptBits & (1 << IsStarGenerator))); @@ -975,6 +991,7 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, uint32_t lineno; uint32_t column; uint64_t packedFields; + uint32_t numFieldInitializers; if (mode == XDR_ENCODE) { // Note: it's possible the LazyScript has a non-null script_ pointer @@ -990,13 +1007,19 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, lineno = lazy->lineno(); column = lazy->column(); packedFields = lazy->packedFields(); + if (fun->kind() == JSFunction::FunctionKind::ClassConstructor) { + numFieldInitializers = (uint32_t)lazy->getFieldInitializers().numFieldInitializers; + } else { + numFieldInitializers = UINT32_MAX; + } } if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) || !xdr->codeUint32(&toStringStart) || !xdr->codeUint32(&toStringEnd) || !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || - !xdr->codeUint64(&packedFields)) + !xdr->codeUint64(&packedFields) || + !xdr->codeUint32(&numFieldInitializers)) { return false; } @@ -1007,6 +1030,9 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, if (!lazy) return false; lazy->setToStringEnd(toStringEnd); + if (numFieldInitializers != UINT32_MAX) { + lazy->setFieldInitializers(FieldInitializers((size_t)numFieldInitializers)); + } fun->initLazyScript(lazy); } } @@ -2786,6 +2812,7 @@ JSScript::initFromFunctionBox(ExclusiveContext* cx, HandleScript script, script->funHasExtensibleScope_ = funbox->hasExtensibleScope(); script->needsHomeObject_ = funbox->needsHomeObject(); script->isDerivedClassConstructor_ = funbox->isDerivedClassConstructor(); + script->isFieldInitializer_ = funbox->isFieldInitializer(); if (funbox->argumentsHasLocalBinding()) { script->setArgumentsHasVarBinding(); @@ -3464,6 +3491,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, dst->isGeneratorExp_ = src->isGeneratorExp(); dst->setGeneratorKind(src->generatorKind()); dst->isDerivedClassConstructor_ = src->isDerivedClassConstructor(); + dst->isFieldInitializer_ = src->isFieldInitializer(); dst->needsHomeObject_ = src->needsHomeObject(); dst->isDefaultClassConstructor_ = src->isDefaultClassConstructor(); dst->isAsync_ = src->asyncKind() == AsyncFunction; @@ -4110,6 +4138,7 @@ LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields, sourceObject_(nullptr), table_(table), packedFields_(packedFields), + fieldInitializers_(FieldInitializers::Invalid()), begin_(begin), end_(end), toStringStart_(toStringStart), diff --git a/js/src/jsscript.h b/js/src/jsscript.h index e2225f0cda..5d6c09dba6 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -704,6 +704,35 @@ class ScriptSourceObject : public NativeObject enum GeneratorKind { NotGenerator, LegacyGenerator, StarGenerator }; enum FunctionAsyncKind { SyncFunction, AsyncFunction }; +struct FieldInitializers +{ +#ifdef DEBUG + bool valid; +#endif + // This struct will eventually have a vector of constant values for optimizing + // field initializers. + size_t numFieldInitializers; + + explicit FieldInitializers(size_t numFieldInitializers) + : +#ifdef DEBUG + valid(true), +#endif + numFieldInitializers(numFieldInitializers) { + } + + static FieldInitializers Invalid() { return FieldInitializers(); } + + private: + FieldInitializers() + : +#ifdef DEBUG + valid(false), +#endif + numFieldInitializers(0) { + } +}; + static inline unsigned GeneratorKindAsBits(GeneratorKind generatorKind) { return static_cast(generatorKind); @@ -855,6 +884,8 @@ class JSScript : public js::gc::TenuredCell private: js::SharedScriptData* scriptData_; + + js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid(); public: uint8_t* data; /* pointer to variable-length data array (see comment above Create() for details) */ @@ -1089,6 +1120,8 @@ class JSScript : public js::gc::TenuredCell bool isDerivedClassConstructor_:1; bool isDefaultClassConstructor_:1; + bool isFieldInitializer_:1; + bool isAsync_:1; bool hasRest_:1; @@ -1098,7 +1131,10 @@ class JSScript : public js::gc::TenuredCell // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - // Currently no padding is needed. +# ifndef DEBUG + // DEBUG is currently 4 bytes larger and doesn't need padding to gc::CellSize + uint32_t padding_; +# endif #endif // @@ -1427,6 +1463,10 @@ class JSScript : public js::gc::TenuredCell return isDerivedClassConstructor_; } + bool isFieldInitializer() const { + return isFieldInitializer_; + } + /* * As an optimization, even when argsHasLocalBinding, the function prologue * may not need to create an arguments object. This is determined by @@ -1454,6 +1494,11 @@ class JSScript : public js::gc::TenuredCell return functionHasThisBinding_; } + void setFieldInitializers(js::FieldInitializers fieldInitializers) { + fieldInitializers_ = fieldInitializers; + } + const js::FieldInitializers& getFieldInitializers() const { return fieldInitializers_; } + /* * Arguments access (via JSOP_*ARG* opcodes) must access the canonical * location for the argument. If an arguments object exists AND it's mapped @@ -2003,7 +2048,6 @@ namespace js { // bytecode from its source. class LazyScript : public gc::TenuredCell { - private: // If non-nullptr, the script has been compiled and this is a forwarding // pointer to the result. This is a weak pointer: after relazification, we // can collect the script if there are no other pointers to it. @@ -2030,7 +2074,6 @@ class LazyScript : public gc::TenuredCell uint32_t padding; #endif - private: static const uint32_t NumClosedOverBindingsBits = 20; static const uint32_t NumInnerFunctionsBits = 20; @@ -2062,6 +2105,7 @@ class LazyScript : public gc::TenuredCell uint32_t hasBeenCloned : 1; uint32_t treatAsRunOnce : 1; uint32_t isDerivedClassConstructor : 1; + uint32_t isFieldInitializer : 1; uint32_t needsHomeObject : 1; uint32_t hasRest : 1; uint32_t parseGoal : 1; @@ -2072,6 +2116,8 @@ class LazyScript : public gc::TenuredCell uint64_t packedFields_; }; + FieldInitializers fieldInitializers_; + // Source location for the script. // See the comment in JSScript for the details. uint32_t begin_; @@ -2276,6 +2322,13 @@ class LazyScript : public gc::TenuredCell p_.isDerivedClassConstructor = true; } + bool isFieldInitializer() const { + return p_.isFieldInitializer; + } + void setIsFieldInitializer() { + p_.isFieldInitializer = true; + } + bool needsHomeObject() const { return p_.needsHomeObject; } @@ -2297,6 +2350,12 @@ class LazyScript : public gc::TenuredCell p_.hasThisBinding = true; } + void setFieldInitializers(FieldInitializers fieldInitializers) { + fieldInitializers_ = fieldInitializers; + } + + const FieldInitializers& getFieldInitializers() const { return fieldInitializers_; } + const char* filename() const { return scriptSource()->filename(); } diff --git a/js/src/moz.build b/js/src/moz.build index 3553dc9bf1..b12d0a90cc 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -140,14 +140,18 @@ main_deunified_sources = [ 'frontend/BytecodeControlStructures.cpp', 'frontend/BytecodeEmitter.cpp', 'frontend/CallOrNewEmitter.cpp', + 'frontend/DefaultEmitter.cpp', 'frontend/ElemOpEmitter.cpp', 'frontend/EmitterScope.cpp', 'frontend/FoldConstants.cpp', 'frontend/ForOfLoopControl.cpp', + 'frontend/FunctionEmitter.cpp', 'frontend/IfEmitter.cpp', 'frontend/JumpList.cpp', + 'frontend/LexicalScopeEmitter.cpp', 'frontend/NameFunctions.cpp', 'frontend/NameOpEmitter.cpp', + 'frontend/ObjectEmitter.cpp', 'frontend/ParseNode.cpp', 'frontend/PropOpEmitter.cpp', 'frontend/SwitchEmitter.cpp', diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 3304ae0305..e1e9f56c31 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -16,6 +16,7 @@ macro(anonymous, anonymous, "anonymous") \ macro(Any, Any, "Any") \ macro(apply, apply, "apply") \ + macro(args, args, "args") \ macro(arguments, arguments, "arguments") \ macro(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \ macro(ArrayIterator, ArrayIterator, "Array Iterator") \ @@ -102,6 +103,10 @@ macro(dotAll, dotAll, "dotAll") \ macro(dotGenerator, dotGenerator, ".generator") \ macro(dotThis, dotThis, ".this") \ + macro(dotInitializers, dotInitializers, ".initializers") \ + macro(dotFieldKeys, dotFieldKeys, ".fieldKeys") \ + macro(dotStaticInitializers, dotStaticInitializers, ".staticInitializers") \ + macro(dotStaticFieldKeys, dotStaticFieldKeys, ".staticFieldKeys") \ macro(each, each, "each") \ macro(elementType, elementType, "elementType") \ macro(else, else_, "else") \ diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 8624b5345e..f6856f2290 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1890,6 +1890,7 @@ macro(JSOP_UNPICK, 183,"unpick", NULL, 2, 0, 0, JOF_UINT8) \ /* * Pops the top of stack value, pushes property of it onto the stack. + * Requires the value under 'obj' to be the receiver of the following call. * * Like JSOP_GETPROP but for call context. * Category: Literals @@ -1974,7 +1975,8 @@ \ /* * Pops the top two values on the stack as 'propval' and 'obj', pushes - * 'propval' property of 'obj' onto the stack. + * 'propval' property of 'obj' onto the stack. Requires the value under + * 'obj' to be the receiver of the following call. * * Like JSOP_GETELEM but for call context. * Category: Literals diff --git a/js/src/vm/Scope.cpp b/js/src/vm/Scope.cpp index 5cb9abaecf..60d845836e 100644 --- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -761,8 +761,8 @@ FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosin MOZ_ASSERT(!data->nextFrameSlot); } - scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, needsEnvironment, fun, - enclosing)); + scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, + needsEnvironment, fun, enclosing)); if (!scope) return false; diff --git a/js/src/vm/Scope.h b/js/src/vm/Scope.h index fc1419bb89..b4e83dfc9c 100644 --- a/js/src/vm/Scope.h +++ b/js/src/vm/Scope.h @@ -548,8 +548,9 @@ class FunctionScope : public Scope private: static FunctionScope* createWithData(ExclusiveContext* cx, MutableHandle> data, - bool hasParameterExprs, bool needsEnvironment, - HandleFunction fun, HandleScope enclosing); + bool hasParameterExprs, + bool needsEnvironment, HandleFunction fun, + HandleScope enclosing); Data& data() { return *reinterpret_cast(data_); diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 98fcff4203..a56d4a3830 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -7045,7 +7045,7 @@ ParseFunction(ModuleValidator& m, FunctionNode** funNodeOut, unsigned* line) return false; FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; - FunctionNode* funNode = m.parser().handler.newFunction(syntaxKind); + FunctionNode* funNode = m.parser().handler.newFunction(syntaxKind, m.parser().pos()); if (!funNode) return false;