diff options
Diffstat (limited to 'js/src/frontend')
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 1095 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 54 | ||||
-rw-r--r-- | js/src/frontend/FoldConstants.cpp | 33 | ||||
-rw-r--r-- | js/src/frontend/FullParseHandler.h | 37 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.cpp | 5 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 108 | ||||
-rw-r--r-- | js/src/frontend/Parser.cpp | 369 | ||||
-rw-r--r-- | js/src/frontend/Parser.h | 17 | ||||
-rw-r--r-- | js/src/frontend/SourceNotes.h | 2 | ||||
-rw-r--r-- | js/src/frontend/SyntaxParseHandler.h | 21 | ||||
-rw-r--r-- | js/src/frontend/TokenKind.h | 1 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.cpp | 21 |
12 files changed, 1556 insertions, 207 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index fe5ef69f7..b5a96f32d 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -59,6 +59,7 @@ class LabelControl; class LoopControl; class ForOfLoopControl; class TryFinallyControl; +class OptionalEmitter; static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) @@ -1985,6 +1986,81 @@ class MOZ_STACK_CLASS IfThenElseEmitter #endif }; +// Class for emitting bytecode for optional expressions. +class MOZ_RAII OptionalEmitter +{ + public: + OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth); + + private: + BytecodeEmitter* bce_; + + BytecodeEmitter::TDZCheckCache tdzCache_; + + // Jump target for short circuiting code, which has null or undefined values. + JumpList jumpShortCircuit_; + + // Jump target for code that does not short circuit. + JumpList jumpFinish_; + + // Stack depth when the optional emitter was instantiated. + int32_t initialDepth_; + + // The state of this emitter. + // + // +-------+ emitJumpShortCircuit +--------------+ + // | Start |-+---------------------------->| ShortCircuit |-----------+ + // +-------+ | +--------------+ | + // +----->| | + // | | emitJumpShortCircuitForCall +---------------------+ v + // | +---------------------------->| ShortCircuitForCall |--->+ + // | +---------------------+ | + // | | + // ---------------------------------------------------------------+ + // | + // | + // +------------------------------------------------------------------+ + // | + // | emitOptionalJumpTarget +---------+ + // +----------------------->| JumpEnd | + // +---------+ + // +#ifdef DEBUG + enum class State { + // The initial state. + Start, + + // for shortcircuiting in most cases. + ShortCircuit, + + // for shortcircuiting from references, which have two items on + // the stack. For example function calls. + ShortCircuitForCall, + + // internally used, end of the jump code + JumpEnd + }; + + State state_ = State::Start; +#endif + + public: + enum class Kind { + // Requires two values on the stack + Reference, + // Requires one value on the stack + Other + }; + + MOZ_MUST_USE bool emitJumpShortCircuit(); + MOZ_MUST_USE bool emitJumpShortCircuitForCall(); + + // JSOp is the op code to be emitted, Kind is if we are dealing with a + // reference (in which case we need two elements on the stack) or other value + // (which needs one element on the stack) + MOZ_MUST_USE bool emitOptionalJumpTarget(JSOp op, Kind kind = Kind::Other); +}; + class ForOfLoopControl : public LoopControl { using EmitterScope = BytecodeEmitter::EmitterScope; @@ -3132,6 +3208,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) // Watch out for getters! case PNK_DOT: + case PNK_OPTDOT: MOZ_ASSERT(pn->isArity(PN_NAME)); *answer = true; return true; @@ -3214,6 +3291,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_DELETENAME: case PNK_DELETEPROP: case PNK_DELETEELEM: + case PNK_DELETEOPTCHAIN: MOZ_ASSERT(pn->isArity(PN_UNARY)); *answer = true; return true; @@ -3318,6 +3396,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) // More getters. case PNK_ELEM: + case PNK_OPTELEM: MOZ_ASSERT(pn->isArity(PN_BINARY)); *answer = true; return true; @@ -3375,12 +3454,21 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) // Function calls can invoke non-local code. case PNK_NEW: case PNK_CALL: + case PNK_OPTCALL: case PNK_TAGGED_TEMPLATE: case PNK_SUPERCALL: MOZ_ASSERT(pn->isArity(PN_LIST)); *answer = true; return true; + // Function arg lists can contain arbitrary expressions. Technically + // this only causes side-effects if one of the arguments does, but since + // the call being made will always trigger side-effects, it isn't needed. + case PNK_OPTCHAIN: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + *answer = true; + return true; + // Classes typically introduce names. Even if no name is introduced, // the heritage and/or class body (through computed property names) // usually have effects. @@ -5274,6 +5362,55 @@ BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, Destructuri return true; } +bool BytecodeEmitter::emitPushNotUndefinedOrNull() +{ + // [stack] V + MOZ_ASSERT(stackDepth > 0); + + if (!emit1(JSOP_DUP)) { + // [stack] V V + return false; + } + if (!emit1(JSOP_UNDEFINED)) { + // [stack] V V UNDEFINED + return false; + } + if (!emit1(JSOP_NE)) { + // [stack] V NEQ + return false; + } + + JumpList undefinedOrNullJump; + if (!emitJump(JSOP_AND, &undefinedOrNullJump)) { + // [stack] V NEQ + return false; + } + + if (!emit1(JSOP_POP)) { + // [stack] V + return false; + } + if (!emit1(JSOP_DUP)) { + // [stack] V V + return false; + } + if (!emit1(JSOP_NULL)) { + // [stack] V V NULL + return false; + } + if (!emit1(JSOP_NE)) { + // [stack] V NEQ + return false; + } + + if (!emitJumpTargetAndPatch(undefinedOrNullJump)) { + // [stack] V NOT-UNDEF-OR-NULL + return false; + } + + return true; +} + bool BytecodeEmitter::emitIteratorNext(ParseNode* pn, IteratorKind iterKind /* = IteratorKind::Sync */, bool allowSelfHosted /* = false */) @@ -9252,6 +9389,116 @@ BytecodeEmitter::emitDeleteExpression(ParseNode* node) return emit1(JSOP_TRUE); } +bool +BytecodeEmitter::emitDeleteOptionalChain(ParseNode* deleteNode) +{ + MOZ_ASSERT(deleteNode->isKind(PNK_DELETEOPTCHAIN)); + + OptionalEmitter oe(this, stackDepth); + + ParseNode* kid = deleteNode->pn_kid; + switch (kid->getKind()) { + case PNK_ELEM: + case PNK_OPTELEM: { + PropertyByValueBase* elemExpr = &kid->as<PropertyByValueBase>(); + if (!emitDeleteElementInOptChain(elemExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] TRUE + return false; + } + break; + } + case PNK_DOT: + case PNK_OPTDOT: { + PropertyAccessBase* propExpr = &kid->as<PropertyAccessBase>(); + if (!emitDeletePropertyInOptChain(propExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] TRUE + return false; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unrecognized optional delete ParseNodeKind"); + } + + if (!oe.emitOptionalJumpTarget(JSOP_TRUE)) { + // [stack] # If shortcircuit + // [stack] TRUE + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitDeletePropertyInOptChain( + PropertyAccessBase* propExpr, + OptionalEmitter& oe) +{ + MOZ_ASSERT_IF(propExpr->is<PropertyAccess>(), + !propExpr->as<PropertyAccess>().isSuper()); + + if (!emitOptionalTree(&propExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + if (propExpr->isKind(PNK_OPTDOT)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP; + if (!emitAtomOp(propExpr, delOp)) { + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitDeleteElementInOptChain( + PropertyByValueBase* elemExpr, + OptionalEmitter& oe) +{ + MOZ_ASSERT_IF(elemExpr->is<PropertyByValue>(), + !elemExpr->as<PropertyByValue>().isSuper()); + + if (!emitOptionalTree(elemExpr->pn_left, oe)) { + // [stack] OBJ + return false; + } + + if (elemExpr->isKind(PNK_OPTELEM)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!emitTree(elemExpr->pn_right)) { + // [stack] OBJ KEY + return false; + } + + JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; + return emitElemOpBase(delOp); +} + static const char * SelfHostedCallFunctionName(JSAtom* name, ExclusiveContext* cx) { @@ -9473,10 +9720,152 @@ BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitte return true; } +/* A version of emitCalleeAndThis for the optional cases: + * * a?.() + * * a?.b() + * * a?.["b"]() + * * (a?.b)() + * + * See emitCallOrNew and emitOptionalCall for more context. + */ bool -BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */) +BytecodeEmitter::emitOptionalCalleeAndThis( + ParseNode* callNode, + ParseNode* calleeNode, + bool isCall, + OptionalEmitter& oe) +{ + JS_CHECK_RECURSION(cx, return false); + + switch (calleeNode->getKind()) { + case PNK_NAME: { + if (!emitGetName(calleeNode, isCall)) { + return false; + } + break; + } + case PNK_OPTDOT: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyAccess* prop = &calleeNode->as<OptionalPropertyAccess>(); + if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_DOT: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &calleeNode->as<PropertyAccess>(); + if (!emitOptionalDotExpression(prop, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_OPTELEM: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyByValue* elem = &calleeNode->as<OptionalPropertyByValue>(); + if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_ELEM: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyByValue* elem = &calleeNode->as<PropertyByValue>(); + if (!emitOptionalElemExpression(elem, oe, calleeNode, isCall)) { + return false; + } + break; + } + case PNK_FUNCTION: { + /* + * Top level lambdas which are immediately invoked should be + * treated as only running once. Every time they execute we will + * create new types and scripts for their contents, to increase + * the quality of type information within them and enable more + * backend optimizations. Note that this does not depend on the + * lambda being invoked at most once (it may be named or be + * accessed via foo.caller indirection), as multiple executions + * will just cause the inner scripts to be repeatedly cloned. + */ + MOZ_ASSERT(!emittingRunOnceLambda); + if (checkRunOnceContext()) { + emittingRunOnceLambda = true; + if (!emitOptionalTree(calleeNode, oe)) { + return false; + } + emittingRunOnceLambda = false; + } else { + if (!emitOptionalTree(calleeNode, oe)) { + return false; + } + } + isCall = false; + break; + } + case PNK_OPTCHAIN: { + return emitCalleeAndThisForOptionalChain(calleeNode, callNode, isCall); + } + default: { + MOZ_RELEASE_ASSERT(calleeNode->getKind() != PNK_SUPERBASE); + if (!emitOptionalTree(calleeNode, oe)) { + return false; + } + isCall = false; /* trigger JSOP_UNDEFINED after */ + break; + } + } + + if (!emitCallOrNewThis(callNode, isCall)) { + return false; + } + + return true; +} + +/* + * A modified version of emitCallOrNew that handles optional calls. + * + * These include the following: + * a?.() + * a.b?.() + * a.["b"]?.() + * (a?.b)?.() + * + * See CallOrNewEmitter for more context. + */ +bool +BytecodeEmitter::emitOptionalCall( + ParseNode* callNode, + OptionalEmitter& oe, + ValueUsage valueUsage) +{ + bool isCall = true; + ParseNode* calleeNode = callNode->pn_head; + + if (!emitOptionalCalleeAndThis(callNode, calleeNode, isCall, oe)) { + // [stack] CALLEE THIS + return false; + } + + if (callNode->isKind(PNK_OPTCALL)) { + if (!oe.emitJumpShortCircuitForCall()) { + // [stack] CALLEE THIS + return false; + } + } + + if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) { + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitCallOrNew( + ParseNode* callNode, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { - bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE); /* * Emit callable invocation or operator new (constructor call) code. * First, emit code for the left operand to evaluate the callable or @@ -9492,65 +9881,82 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs * value required for calls (which non-strict mode functions * will box into the global object). */ - uint32_t argc = pn->pn_count - 1; + ParseNode* calleeNode = callNode->pn_head; + bool isCall = callNode->isKind(PNK_CALL) || callNode->isKind(PNK_TAGGED_TEMPLATE); + + if (calleeNode->isKind(PNK_NAME) && + emitterMode == BytecodeEmitter::SelfHosting && + !IsSpreadOp(callNode->getOp())) { + // Calls to "forceInterpreter", "callFunction", + // "callContentFunction", or "resumeGenerator" in self-hosted + // code generate inline bytecode. + PropertyName* calleeName = calleeNode->name(); + if (calleeName == cx->names().callFunction || + calleeName == cx->names().callContentFunction || + calleeName == cx->names().constructContentFunction) + { + return emitSelfHostedCallFunction(callNode); + } + if (calleeName == cx->names().resumeGenerator) + return emitSelfHostedResumeGenerator(callNode); + if (calleeName == cx->names().forceInterpreter) + return emitSelfHostedForceInterpreter(callNode); + if (calleeName == cx->names().allowContentIter) + return emitSelfHostedAllowContentIter(callNode); + // Fall through. + } - if (argc >= ARGC_LIMIT) { - parser->tokenStream.reportError(callop - ? JSMSG_TOO_MANY_FUN_ARGS - : JSMSG_TOO_MANY_CON_ARGS); + if (!emitCalleeAndThis(callNode, calleeNode, isCall)) { return false; } - ParseNode* pn2 = pn->pn_head; - bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; - switch (pn2->getKind()) { + if (!emitCallOrNewArgumentsAndEnd(callNode, calleeNode, isCall, valueUsage)) { + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitCalleeAndThis( + ParseNode* callNode, + ParseNode* calleeNode, + bool isCall) +{ + switch (calleeNode->getKind()) { case PNK_NAME: - if (emitterMode == BytecodeEmitter::SelfHosting && !spread) { - // Calls to "forceInterpreter", "callFunction", - // "callContentFunction", or "resumeGenerator" in self-hosted - // code generate inline bytecode. - if (pn2->name() == cx->names().callFunction || - pn2->name() == cx->names().callContentFunction || - pn2->name() == cx->names().constructContentFunction) - { - return emitSelfHostedCallFunction(pn); - } - if (pn2->name() == cx->names().resumeGenerator) - return emitSelfHostedResumeGenerator(pn); - if (pn2->name() == cx->names().forceInterpreter) - return emitSelfHostedForceInterpreter(pn); - if (pn2->name() == cx->names().allowContentIter) - return emitSelfHostedAllowContentIter(pn); - // Fall through. - } - if (!emitGetName(pn2, callop)) + if (!emitGetName(calleeNode, isCall)) { return false; + } break; case PNK_DOT: MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); - if (pn2->as<PropertyAccess>().isSuper()) { - if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop)) + if (calleeNode->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropOp(calleeNode, JSOP_GETPROP_SUPER, isCall)) { return false; + } } else { - if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP)) + if (!emitPropOp(calleeNode, isCall ? JSOP_CALLPROP : JSOP_GETPROP)) { return false; + } } - break; case PNK_ELEM: MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); - if (pn2->as<PropertyByValue>().isSuper()) { - if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop)) + if (calleeNode->as<PropertyByValue>().isSuper()) { + if (!emitSuperElemOp(calleeNode, JSOP_GETELEM_SUPER, isCall)) { return false; + } } else { - if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM)) + if (!emitElemOp(calleeNode, isCall ? JSOP_CALLELEM : JSOP_GETELEM)) { return false; - if (callop) { - if (!emit1(JSOP_SWAP)) + } + if (isCall) { + if (!emit1(JSOP_SWAP)) { return false; + } } } - break; case PNK_FUNCTION: /* @@ -9566,115 +9972,156 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs MOZ_ASSERT(!emittingRunOnceLambda); if (checkRunOnceContext()) { emittingRunOnceLambda = true; - if (!emitTree(pn2)) + if (!emitTree(calleeNode)) { return false; + } emittingRunOnceLambda = false; } else { - if (!emitTree(pn2)) + if (!emitTree(calleeNode)) { return false; + } } - callop = false; + isCall = false; break; case PNK_SUPERBASE: - MOZ_ASSERT(pn->isKind(PNK_SUPERCALL)); - MOZ_ASSERT(parser->handler.isSuperBase(pn2)); - if (!emit1(JSOP_SUPERFUN)) + MOZ_ASSERT(callNode->isKind(PNK_SUPERCALL)); + MOZ_ASSERT(parser->handler.isSuperBase(calleeNode)); + if (!emit1(JSOP_SUPERFUN)) { return false; + } break; + case PNK_OPTCHAIN: + return emitCalleeAndThisForOptionalChain(calleeNode, callNode, isCall); default: - if (!emitTree(pn2)) + if (!emitTree(calleeNode)) { return false; - callop = false; /* trigger JSOP_UNDEFINED after */ + } + isCall = false; /* trigger JSOP_UNDEFINED after */ break; } - bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || - pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL; + if (!emitCallOrNewThis(callNode, isCall)) { + return false; + } + return true; +} +// NOTE: Equivalent to CallOrNewEmitter::emitThis +bool +BytecodeEmitter::emitCallOrNewThis( + ParseNode* callNode, + bool isCall) +{ // Emit room for |this|. - if (!callop) { - if (isNewOp) { - if (!emit1(JSOP_IS_CONSTRUCTING)) - return false; + if (!isCall) { + JSOp opForEmit; + if (IsNewOp(callNode->getOp())) { + opForEmit = JSOP_IS_CONSTRUCTING; } else { - if (!emit1(JSOP_UNDEFINED)) - return false; + opForEmit = JSOP_UNDEFINED; + } + if (!emit1(opForEmit)) { + return false; } } + return true; +} + +// NOTE: The following function is equivalent to CallOrNewEmitter::emitEnd +// and BytecodeEmitter::emitArguments in a future refactor +bool +BytecodeEmitter::emitCallOrNewArgumentsAndEnd( + ParseNode* callNode, + ParseNode* calleeNode, + bool isCall, + ValueUsage valueUsage) +{ + uint32_t argumentCount = callNode->pn_count - 1; + if (argumentCount >= ARGC_LIMIT) { + parser->tokenStream.reportError(isCall + ? JSMSG_TOO_MANY_FUN_ARGS + : JSMSG_TOO_MANY_CON_ARGS); + return false; + } + + JSOp op = callNode->getOp(); + bool isSpread = IsSpreadOp(op); + /* * Emit code for each argument in order, then emit the JSOP_*CALL or * JSOP_NEW bytecode with a two-byte immediate telling how many args * were pushed on the operand stack. */ - if (!spread) { - for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) { - if (!emitTree(pn3)) + ParseNode* argumentNode = calleeNode->pn_next; + if (!isSpread) { + while (argumentNode) { + if (!emitTree(argumentNode)) { return false; - } - - if (isNewOp) { - if (pn->isKind(PNK_SUPERCALL)) { - if (!emit1(JSOP_NEWTARGET)) - return false; - } else { - // Repush the callee as new.target - if (!emitDupAt(argc + 1)) - return false; } + argumentNode = argumentNode->pn_next; } } else { - ParseNode* args = pn2->pn_next; JumpList jmp; bool optCodeEmitted = false; - if (argc == 1) { - if (!emitOptimizeSpread(args->pn_kid, &jmp, &optCodeEmitted)) + if (argumentCount == 1) { + if (!emitOptimizeSpread(argumentNode->pn_kid, &jmp, + &optCodeEmitted)) { return false; + } } - if (!emitArray(args, argc, JSOP_SPREADCALLARRAY)) + if (!emitArray(argumentNode, argumentCount, JSOP_SPREADCALLARRAY)) { return false; + } if (optCodeEmitted) { - if (!emitJumpTargetAndPatch(jmp)) + if (!emitJumpTargetAndPatch(jmp)) { return false; - } - - if (isNewOp) { - if (pn->isKind(PNK_SUPERCALL)) { - if (!emit1(JSOP_NEWTARGET)) - return false; - } else { - if (!emitDupAt(2)) - return false; } } } - if (!spread) { - if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) { - if (!emitCall(JSOP_CALL_IGNORES_RV, argc, pn)) + if (IsNewOp(op)) { + if (callNode->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) { return false; - checkTypeSet(JSOP_CALL_IGNORES_RV); + } } else { - if (!emitCall(pn->getOp(), argc, pn)) + // Repush the callee as new.target + uint32_t finalArgumentCount = argumentCount + 1; + if (isSpread) { + finalArgumentCount = 2; + } + if (!emitDupAt(finalArgumentCount)) { return false; - checkTypeSet(pn->getOp()); + } + } + } + + if (!isSpread) { + JSOp callOp = op; + if (callOp == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) { + callOp = JSOP_CALL_IGNORES_RV; + } + if (!emitCall(callOp, argumentCount, callNode)) { + return false; } + checkTypeSet(callOp); } else { - if (!emit1(pn->getOp())) + if (!emit1(op)) { return false; - checkTypeSet(pn->getOp()); + } + checkTypeSet(op); } - if (pn->isOp(JSOP_EVAL) || - pn->isOp(JSOP_STRICTEVAL) || - pn->isOp(JSOP_SPREADEVAL) || - pn->isOp(JSOP_STRICTSPREADEVAL)) - { - uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); - if (!emitUint32Operand(JSOP_LINENO, lineNum)) + + if (IsEvalOp(op)) { + uint32_t lineNum = + parser->tokenStream.srcCoords.lineNum(callNode->pn_pos.begin); + if (!emitUint32Operand(JSOP_LINENO, lineNum)) { return false; + } } return true; @@ -10992,6 +11439,18 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_DELETEOPTCHAIN: + if (!emitDeleteOptionalChain(pn)) { + return false; + } + break; + + case PNK_OPTCHAIN: + if (!emitOptionalChain(pn, valueUsage)) { + return false; + } + break; + case PNK_DOT: if (pn->as<PropertyAccess>().isSuper()) { if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) @@ -11184,6 +11643,283 @@ BytecodeEmitter::emitTreeInBranch(ParseNode* pn, return emitTree(pn, valueUsage); } +/* + * Special `emitTree` for Optional Chaining case. + * Examples of this are `emitOptionalChain`, and `emitDeleteOptionalChain`. + */ +bool +BytecodeEmitter::emitOptionalTree( + ParseNode* pn, + OptionalEmitter& oe, + ValueUsage valueUsage /* = ValueUsage::WantValue */) +{ + JS_CHECK_RECURSION(cx, return false); + + ParseNodeKind kind = pn->getKind(); + switch (kind) { + case PNK_OPTDOT: { + OptionalPropertyAccess* prop = &pn->as<OptionalPropertyAccess>(); + if (!emitOptionalDotExpression(prop, oe, pn, false)) { + return false; + } + break; + } + case PNK_DOT: { + PropertyAccess* prop = &pn->as<PropertyAccess>(); + if (!emitOptionalDotExpression(prop, oe, pn, false)) { + return false; + } + break; + } + case PNK_OPTELEM: { + OptionalPropertyByValue* elem = &pn->as<OptionalPropertyByValue>(); + if (!emitOptionalElemExpression(elem, oe, pn, false)) { + return false; + } + break; + } + case PNK_ELEM: { + PropertyByValue* elem = &pn->as<PropertyByValue>(); + if (!emitOptionalElemExpression(elem, oe, pn, false)) { + return false; + } + break; + } + case PNK_CALL: + case PNK_OPTCALL: { + if (!emitOptionalCall(pn, oe, valueUsage)) { + return false; + } + break; + } + // List of accepted ParseNodeKinds that might appear only at the beginning + // of an Optional Chain. + // For example, a taggedTemplateExpr node might occur if we have + // `test`?.b, with `test` as the taggedTemplateExpr ParseNode. + default: { +#ifdef DEBUG + // https://tc39.es/ecma262/#sec-primary-expression + bool isPrimaryExpression = + kind == PNK_THIS || + kind == PNK_NAME || + kind == PNK_NULL || + kind == PNK_TRUE || + kind == PNK_FALSE || + kind == PNK_NUMBER || + kind == PNK_STRING || + kind == PNK_ARRAY || + kind == PNK_OBJECT || + kind == PNK_FUNCTION || + kind == PNK_CLASS || + kind == PNK_REGEXP || + kind == PNK_TEMPLATE_STRING || + kind == PNK_RAW_UNDEFINED || + pn->isInParens(); + + // https://tc39.es/ecma262/#sec-left-hand-side-expressions + bool isMemberExpression = isPrimaryExpression || + kind == PNK_TAGGED_TEMPLATE || + kind == PNK_NEW || + kind == PNK_NEWTARGET; + //kind == ParseNodeKind::ImportMetaExpr; + + bool isCallExpression = kind == PNK_SETTHIS; + //kind == ParseNodeKind::CallImportExpr; + + MOZ_ASSERT(isMemberExpression || isCallExpression, + "Unknown ParseNodeKind for OptionalChain"); +#endif + return emitTree(pn); + } + } + + return true; +} + +// Handle the case of a call made on a OptionalChainParseNode. +// For example `(a?.b)()` and `(a?.b)?.()`. +bool +BytecodeEmitter::emitCalleeAndThisForOptionalChain( + ParseNode* optionalChain, + ParseNode* callNode, + bool isCall) +{ + ParseNode* calleeNode = optionalChain->pn_kid; + + // Create a new OptionalEmitter, in order to emit the right bytecode + // in isolation. + OptionalEmitter oe(this, stackDepth); + + if (!emitOptionalCalleeAndThis(callNode, calleeNode, isCall, oe)) { + // [stack] CALLEE THIS + return false; + } + + // Complete the jump if necessary. This will set both the "this" value + // and the "callee" value to undefined, if the callee is undefined. It + // does not matter much what the this value is, the function call will + // fail if it is not optional, and be set to undefined otherwise. + if (!oe.emitOptionalJumpTarget(JSOP_UNDEFINED, + OptionalEmitter::Kind::Reference)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED UNDEFINED + // [stack] # otherwise + // [stack] CALLEE THIS + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitOptionalChain( + ParseNode* optionalChain, + ValueUsage valueUsage) +{ + ParseNode* expression = optionalChain->pn_kid; + + OptionalEmitter oe(this, stackDepth); + + if (!emitOptionalTree(expression, oe, valueUsage)) { + // [stack] VAL + return false; + } + + if (!oe.emitOptionalJumpTarget(JSOP_UNDEFINED)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED + // [stack] # otherwise + // [stack] VAL + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitOptionalDotExpression( + PropertyAccessBase* prop, + OptionalEmitter& oe, + ParseNode* calleeNode, + bool isCall) +{ + bool isSuper = prop->is<PropertyAccess>() && + prop->as<PropertyAccess>().isSuper(); + + ParseNode* base = &prop->expression(); + if (isSuper) { + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(base, oe)) { + // [stack] OBJ + return false; + } + } + + if (prop->isKind(PNK_OPTDOT)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + // XXX emitSuperPropLHS, through emitSuperPropOp, contains a call to + // emitGetThisForSuperBase, which we've already called early in this + // function. However, modifying emitSuperPropLHS to prevent it from + // calling emitGetThisForSuperBase will involve too many changes to + // its callers. So, we duplicate their functionality here. + // + // emitPropOp, on the other hand, calls emitTree again, which is + // unnecessary for our case. + // + // This should be equivalent to PropOpEmitter::emitGet + if (isCall && !emit1(JSOP_DUP)) { + return false; + } + JSOp opForEmit = isCall ? JSOP_CALLPROP : JSOP_GETPROP; + if (isSuper) { + if (!emit1(JSOP_SUPERBASE)) { + return false; + } + opForEmit = JSOP_GETPROP_SUPER; + } + if (!emitAtomOp(calleeNode, opForEmit)) { + return false; + } + if (isCall && !emit1(JSOP_SWAP)) { + return false; + } + + return true; +} + +bool +BytecodeEmitter::emitOptionalElemExpression( + PropertyByValueBase* elem, + OptionalEmitter& oe, + ParseNode* calleeNode, + bool isCall) +{ + bool isSuper = elem->is<PropertyByValue>() && + elem->as<PropertyByValue>().isSuper(); + + if (isSuper) { + if (!emitGetThisForSuperBase(calleeNode)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(calleeNode->pn_left, oe)) { + // [stack] OBJ + return false; + } + } + + if (elem->isKind(PNK_OPTELEM)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + // Note: the following conditional is more-or-less equivalent + // to ElemOpEmitter::prepareForKey in a future refactor + if (isCall) { + if (!emit1(JSOP_DUP)) { + return false; + } + } + + if (!emitTree(calleeNode->pn_right)) { + // [stack] OBJ? OBJ KEY + return false; + } + + // Note: the two (2) conditionals below are more-or-less + // equivalent to ElemOpEmitter::emitGet in a future refactor + if (!emitElemOpBase(isCall ? JSOP_CALLELEM : JSOP_GETELEM)) { + return false; + } + if (isCall) { + if (!emit1(JSOP_SWAP)) { + return false; + } + } + + return true; +} + static bool AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index) { @@ -11407,6 +12143,171 @@ BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes) SN_MAKE_TERMINATOR(&destination[totalCount]); } +OptionalEmitter::OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth) + : bce_(bce), + tdzCache_(bce), + initialDepth_(initialDepth) +{ +} + +bool +OptionalEmitter::emitJumpShortCircuit() { + MOZ_ASSERT(state_ == State::Start || + state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + MOZ_ASSERT(initialDepth_ + 1 == bce_->stackDepth); + + IfThenElseEmitter ifEmitter(bce_); + if (!bce_->emitPushNotUndefinedOrNull()) { + // [stack] OBJ NOT-UNDEFINED-OR-NULL + return false; + } + + if (!bce_->emit1(JSOP_NOT)) { + // [stack] OBJ UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitIf() /* emitThen() */) { + return false; + } + + if (!bce_->newSrcNote(SRC_OPTCHAIN)) { + return false; + } + + if (!bce_->emitJump(JSOP_GOTO, &jumpShortCircuit_)) { + // [stack] UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitEnd()) { + return false; + } +#ifdef DEBUG + state_ = State::ShortCircuit; +#endif + return true; +} + +bool +OptionalEmitter::emitJumpShortCircuitForCall() { + MOZ_ASSERT(state_ == State::Start || + state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + int32_t depth = bce_->stackDepth; + MOZ_ASSERT(initialDepth_ + 2 == depth); + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] THIS CALLEE + return false; + } + + IfThenElseEmitter ifEmitter(bce_); + if (!bce_->emitPushNotUndefinedOrNull()) { + // [stack] THIS CALLEE NOT-UNDEFINED-OR-NULL + return false; + } + + if (!bce_->emit1(JSOP_NOT)) { + // [stack] THIS CALLEE UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitIf() /* emitThen() */) { + return false; + } + + if (!bce_->emit1(JSOP_POP)) { + // [stack] VAL + return false; + } + + if (!bce_->newSrcNote(SRC_OPTCHAIN)) { + return false; + } + + if (!bce_->emitJump(JSOP_GOTO, &jumpShortCircuit_)) { + // [stack] UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitEnd()) { + return false; + } + + bce_->stackDepth = depth; + + if (!bce_->emit1(JSOP_SWAP)) { + // [stack] THIS CALLEE + return false; + } +#ifdef DEBUG + state_ = State::ShortCircuitForCall; +#endif + return true; +} + +bool +OptionalEmitter::emitOptionalJumpTarget(JSOp op, + Kind kind /* = Kind::Other */) { +#ifdef DEBUG + int32_t depth = bce_->stackDepth; +#endif + MOZ_ASSERT(state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + + // if we get to this point, it means that the optional chain did not short + // circuit, so we should skip the short circuiting bytecode. + if (!bce_->emitJump(JSOP_GOTO, &jumpFinish_)) { + // [stack] # if call + // [stack] CALLEE THIS + // [stack] # otherwise, if defined + // [stack] VAL + // [stack] # otherwise + // [stack] UNDEFINED-OR-NULL + return false; + } + + if (!bce_->emitJumpTargetAndPatch(jumpShortCircuit_)) { + // [stack] UNDEFINED-OR-NULL + return false; + } + + // reset stack depth to the depth when we jumped + bce_->stackDepth = initialDepth_ + 1; + + if (!bce_->emit1(JSOP_POP)) { + // [stack] + return false; + } + + if (!bce_->emit1(op)) { + // [stack] JSOP + return false; + } + + if (kind == Kind::Reference) { + if (!bce_->emit1(op)) { + // [stack] JSOP JSOP + return false; + } + } + + MOZ_ASSERT(depth == bce_->stackDepth); + + if (!bce_->emitJumpTargetAndPatch(jumpFinish_)) { + // [stack] # if call + // [stack] CALLEE THIS + // [stack] # otherwise + // [stack] VAL + return false; + } +#ifdef DEBUG + state_ = State::JumpEnd; +#endif + return true; +} + void CGConstList::finish(ConstArray* array) { diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 2c1adf827..bf1154e6e 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -19,6 +19,8 @@ #include "frontend/SourceNotes.h" #include "vm/Interpreter.h" +class OptionalEmitter; + namespace js { namespace frontend { @@ -456,6 +458,11 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitTree(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + // Emit code for the optional tree rooted at pn. + MOZ_MUST_USE bool emitOptionalTree(ParseNode* pn, + OptionalEmitter& oe, + ValueUsage valueUsage = ValueUsage::WantValue); + // Emit code for the tree rooted at pn with its own TDZ cache. MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue); @@ -731,6 +738,10 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitAsyncIterator(); + // XXX currently used only by OptionalEmitter, research still required + // to identify when this was introduced in m-c. + MOZ_MUST_USE bool emitPushNotUndefinedOrNull(); + // Pops iterator from the top of the stack. Pushes the result of |.next()| // onto the stack. MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, IteratorKind kind = IteratorKind::Sync, @@ -776,6 +787,32 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitDeleteElement(ParseNode* pn); MOZ_MUST_USE bool emitDeleteExpression(ParseNode* pn); + // Optional methods which emit Optional Jump Target + MOZ_MUST_USE bool emitOptionalChain(ParseNode* optionalChain, + ValueUsage valueUsage); + MOZ_MUST_USE bool emitCalleeAndThisForOptionalChain(ParseNode* optionalChain, + ParseNode* callNode, + bool isCall); + MOZ_MUST_USE bool emitDeleteOptionalChain(ParseNode* deleteNode); + + // Optional methods which emit a shortCircuit jump. They need to be called by + // a method which emits an Optional Jump Target, see below. + MOZ_MUST_USE bool emitOptionalDotExpression(PropertyAccessBase* prop, + OptionalEmitter& oe, + ParseNode* calleeNode, + bool isCall); + MOZ_MUST_USE bool emitOptionalElemExpression(PropertyByValueBase* elem, + OptionalEmitter& oe, + ParseNode* calleeNode, + bool isCall); + MOZ_MUST_USE bool emitOptionalCall(ParseNode* callNode, + OptionalEmitter& oe, + ValueUsage valueUsage); + MOZ_MUST_USE bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe); + // |op| must be JSOP_TYPEOF or JSOP_TYPEOFEXPR. MOZ_MUST_USE bool emitTypeof(ParseNode* node, JSOp op); @@ -794,7 +831,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result); MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted); - MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue); + MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue); + MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callNode, + ParseNode* calleeNode, + bool isCall); + MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callNode, + ParseNode* calleeNode, + bool isCall, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitCallOrNewThis(ParseNode* callNode, + bool isCall); + MOZ_MUST_USE bool emitCallOrNewArgumentsAndEnd(ParseNode* callNode, + ParseNode* calleeNode, + bool isCall, + ValueUsage valueUsage); + MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn); MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index c348056c4..979af29b4 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -319,6 +319,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_DELETEPROP: case PNK_DELETEELEM: case PNK_DELETEEXPR: + case PNK_DELETEOPTCHAIN: case PNK_POS: case PNK_NEG: case PNK_PREINCREMENT: @@ -368,6 +369,10 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_DOT: case PNK_ELEM: case PNK_CALL: + case PNK_OPTCHAIN: + case PNK_OPTDOT: + case PNK_OPTELEM: + case PNK_OPTCALL: case PNK_NAME: case PNK_TEMPLATE_STRING: case PNK_TEMPLATE_STRING_LIST: @@ -1304,7 +1309,7 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& { ParseNode* node = *nodePtr; - MOZ_ASSERT(node->isKind(PNK_ELEM)); + MOZ_ASSERT(node->isKind(PNK_ELEM) || node->isKind(PNK_OPTELEM)); MOZ_ASSERT(node->isArity(PN_BINARY)); ParseNode*& expr = node->pn_left; @@ -1348,9 +1353,15 @@ FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& // Optimization 3: We have expr["foo"] where foo is not an index. Convert // to a property access (like expr.foo) that optimizes better downstream. - ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end); - if (!dottedAccess) + ParseNode* dottedAccess; + if (node->isKind(PNK_OPTELEM)) { + dottedAccess = parser.handler.newOptionalPropertyAccess(expr, name, node->pn_pos.end); + } else { + dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end); + } + if (!dottedAccess) { return false; + } dottedAccess->setInParens(node->isInParens()); ReplaceNode(nodePtr, dottedAccess); @@ -1522,7 +1533,9 @@ static bool FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, bool inGenexpLambda) { - MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) || + MOZ_ASSERT(node->isKind(PNK_CALL) || + node->isKind(PNK_OPTCALL) || + node->isKind(PNK_SUPERCALL) || node->isKind(PNK_TAGGED_TEMPLATE)); MOZ_ASSERT(node->isArity(PN_LIST)); @@ -1599,13 +1612,13 @@ static bool FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, bool inGenexpLambda) { - MOZ_ASSERT(node->isKind(PNK_DOT)); + MOZ_ASSERT(node->isKind(PNK_DOT) || node->isKind(PNK_OPTDOT)); MOZ_ASSERT(node->isArity(PN_NAME)); // Iterate through a long chain of dotted property accesses to find the // most-nested non-dotted property node, then fold that. ParseNode** nested = &node->pn_expr; - while ((*nested)->isKind(PNK_DOT)) { + while ((*nested)->isKind(PNK_DOT) || (*nested)->isKind(PNK_OPTDOT)) { MOZ_ASSERT((*nested)->isArity(PN_NAME)); nested = &(*nested)->pn_expr; } @@ -1632,6 +1645,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo JS_CHECK_RECURSION(cx, return false); ParseNode* pn = *pnp; +#ifdef DEBUG + ParseNodeKind kind = pn->getKind(); +#endif switch (pn->getKind()) { case PNK_NOP: @@ -1713,6 +1729,8 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo MOZ_ASSERT(pn->isArity(PN_BINARY)); return Fold(cx, &pn->pn_left, parser, inGenexpLambda); + case PNK_DELETEOPTCHAIN: + case PNK_OPTCHAIN: case PNK_SEMI: case PNK_THIS: MOZ_ASSERT(pn->isArity(PN_UNARY)); @@ -1806,6 +1824,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo case PNK_CLASS: return FoldClass(cx, pn, parser, inGenexpLambda); + case PNK_OPTELEM: case PNK_ELEM: return FoldElement(cx, pnp, parser, inGenexpLambda); @@ -1813,6 +1832,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo return FoldAdd(cx, pnp, parser, inGenexpLambda); case PNK_CALL: + case PNK_OPTCALL: case PNK_SUPERCALL: case PNK_TAGGED_TEMPLATE: return FoldCall(cx, pn, parser, inGenexpLambda); @@ -1896,6 +1916,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo MOZ_ASSERT(pn->isArity(PN_NAME)); return Fold(cx, &pn->pn_expr, parser, inGenexpLambda); + case PNK_OPTDOT: case PNK_DOT: return FoldDottedProperty(cx, pn, parser, inGenexpLambda); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index f34635125..eeb4432e4 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -74,6 +74,10 @@ class FullParseHandler return node->isKind(PNK_DOT) || node->isKind(PNK_ELEM); } + bool isOptionalPropertyAccess(ParseNode* node) { + return node->isKind(PNK_OPTDOT) || node->isKind(PNK_OPTELEM); + } + bool isFunctionCall(ParseNode* node) { // Note: super() is a special form, *not* a function call. return node->isKind(PNK_CALL); @@ -213,6 +217,18 @@ class FullParseHandler if (expr->isKind(PNK_ELEM)) return newUnary(PNK_DELETEELEM, JSOP_NOP, begin, expr); + if (expr->isKind(PNK_OPTCHAIN)) { + ParseNode* kid = expr->pn_kid; + // Handle property deletion explicitly. OptionalCall is handled + // via DeleteExpr. + if (kid->isKind(PNK_DOT) || + kid->isKind(PNK_OPTDOT) || + kid->isKind(PNK_ELEM) || + kid->isKind(PNK_OPTELEM)) { + return newUnary(PNK_DELETEOPTCHAIN, JSOP_NOP, begin, kid); + } + } + return newUnary(PNK_DELETEEXPR, JSOP_NOP, begin, expr); } @@ -319,6 +335,10 @@ class FullParseHandler return newList(PNK_CALL, JSOP_CALL); } + ParseNode* newOptionalCall() { + return newList(PNK_OPTCALL, JSOP_CALL); + } + ParseNode* newTaggedTemplate() { return newList(PNK_TAGGED_TEMPLATE, JSOP_CALL); } @@ -459,6 +479,11 @@ class FullParseHandler return new_<UnaryNode>(PNK_AWAIT, JSOP_AWAIT, pos, value); } + ParseNode* newOptionalChain(uint32_t begin, ParseNode* value) { + TokenPos pos(begin, value->pn_pos.end); + return new_<UnaryNode>(PNK_OPTCHAIN, JSOP_NOP, pos, value); + } + // Statements ParseNode* newStatementList(const TokenPos& pos) { @@ -678,6 +703,14 @@ class FullParseHandler return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end); } + ParseNode* newOptionalPropertyAccess(ParseNode* pn, PropertyName* name, uint32_t end) { + return new_<OptionalPropertyAccess>(pn, name, pn->pn_pos.begin, end); + } + + ParseNode* newOptionalPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) { + return new_<OptionalPropertyByValue>(lhs, index, lhs->pn_pos.begin, end); + } + inline MOZ_MUST_USE bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope, ParseNode* catchName, ParseNode* catchGuard, ParseNode* catchBody); @@ -920,7 +953,9 @@ class FullParseHandler return pn->isKind(PNK_CALL); } PropertyName* maybeDottedProperty(ParseNode* pn) { - return pn->is<PropertyAccess>() ? &pn->as<PropertyAccess>().name() : nullptr; + return pn->is<PropertyAccessBase>() ? + &pn->as<PropertyAccessBase>().name() : + nullptr; } JSAtom* isStringExprStatement(ParseNode* pn, TokenPos* pos) { if (JSAtom* atom = pn->isStringExprStatement()) { diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 623346563..dfbd724e2 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -228,6 +228,8 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushUnaryNodeChild(pn, stack); // Nodes with a single nullable child. + case PNK_OPTCHAIN: + case PNK_DELETEOPTCHAIN: case PNK_THIS: case PNK_SEMI: { MOZ_ASSERT(pn->isArity(PN_UNARY)); @@ -253,6 +255,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_MODASSIGN: case PNK_POWASSIGN: // ...and a few others. + case PNK_OPTELEM: case PNK_ELEM: case PNK_IMPORT_SPEC: case PNK_EXPORT_SPEC: @@ -449,6 +452,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_POW: case PNK_COMMA: case PNK_NEW: + case PNK_OPTCALL: case PNK_CALL: case PNK_SUPERCALL: case PNK_GENEXP: @@ -483,6 +487,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) } case PNK_LABEL: + case PNK_OPTDOT: case PNK_DOT: case PNK_NAME: return PushNameNodeChildren(pn, stack); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 8e5e74d3b..0ad0fe088 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -34,6 +34,10 @@ class ObjectBox; F(POSTDECREMENT) \ F(DOT) \ F(ELEM) \ + F(OPTDOT) \ + F(OPTCHAIN) \ + F(OPTELEM) \ + F(OPTCALL) \ F(ARRAY) \ F(ELISION) \ F(STATEMENTLIST) \ @@ -76,6 +80,7 @@ class ObjectBox; F(DELETEPROP) \ F(DELETEELEM) \ F(DELETEEXPR) \ + F(DELETEOPTCHAIN) \ F(TRY) \ F(CATCH) \ F(CATCHLIST) \ @@ -371,6 +376,29 @@ IsTypeofKind(ParseNodeKind kind) * for a more-specific PNK_DELETE* unless constant * folding (or a similar parse tree manipulation) has * occurred + * PNK_DELETEOPTCHAIN unary pn_kid: MEMBER expr that's evaluated, then the + * overall delete evaluates to true; If constant + * folding occurs, PNK_ELEM may become PNK_DOT. + * PNK_OPTELEM does not get folded into PNK_OPTDOT. + * PNK_OPTCHAIN unary pn_kid: expression that is evaluated as a chain. + * Contains one or more optional nodes. Its first node + * is always an optional node, such as PNK_OPTELEM, + * PNK_OPTDOT, or PNK_OPTCALL. This will shortcircuit + * and return 'undefined' without evaluating the rest + * of the expression if any of the optional nodes it + * contains are nullish. An optional chain can also + * contain nodes such as PNK_DOT, PNK_ELEM, PNK_NAME, + * PNK_CALL, etc. These are evaluated normally. + * PNK_OPTDOT name pn_expr: MEMBER expr to left of . + * short circuits back to PNK_OPTCHAIN if nullish. + * pn_atom: name to right of . + * PNK_OPTELEM binary pn_left: MEMBER expr to left of [ + * short circuits back to PNK_OPTCHAIN if nullish. + * pn_right: expr between [ and ] + * PNK_OPTCALL list pn_head: list of call, arg1, arg2, ... argN + * pn_count: 1 + N (where N is number of args) + * call is a MEMBER expr naming a callable object, + * short circuits back to PNK_OPTCHAIN if nullish. * PNK_DOT name pn_expr: MEMBER expr to left of . * pn_atom: name to right of . * PNK_ELEM binary pn_left: MEMBER expr to left of [ @@ -1182,11 +1210,12 @@ class RegExpLiteral : public NullaryNode } }; -class PropertyAccess : public ParseNode +class PropertyAccessBase : public ParseNode { public: - PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, uint32_t end) - : ParseNode(PNK_DOT, JSOP_NOP, PN_NAME, TokenPos(begin, end)) + PropertyAccessBase(ParseNodeKind kind, ParseNode* lhs, PropertyName* name, + uint32_t begin, uint32_t end) + : ParseNode(kind, JSOP_NOP, PN_NAME, TokenPos(begin, end)) { MOZ_ASSERT(lhs != nullptr); MOZ_ASSERT(name != nullptr); @@ -1195,7 +1224,8 @@ class PropertyAccess : public ParseNode } static bool test(const ParseNode& node) { - bool match = node.isKind(PNK_DOT); + bool match = node.isKind(PNK_DOT) || + node.isKind(PNK_OPTDOT); MOZ_ASSERT_IF(match, node.isArity(PN_NAME)); return match; } @@ -1207,6 +1237,24 @@ class PropertyAccess : public ParseNode PropertyName& name() const { return *pn_u.name.atom->asPropertyName(); } +}; + +class PropertyAccess : public PropertyAccessBase +{ + public: + PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, + uint32_t end) + : PropertyAccessBase(PNK_DOT, lhs, name, begin, end) + { + MOZ_ASSERT(lhs != nullptr); + MOZ_ASSERT(name != nullptr); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_DOT); + MOZ_ASSERT_IF(match, node.isArity(PN_NAME)); + return match; + } bool isSuper() const { // PNK_SUPERBASE cannot result from any expression syntax. @@ -1214,17 +1262,50 @@ class PropertyAccess : public ParseNode } }; -class PropertyByValue : public ParseNode +class OptionalPropertyAccess : public PropertyAccessBase +{ + public: + OptionalPropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, + uint32_t end) + : PropertyAccessBase(PNK_OPTDOT, lhs, name, begin, end) + { + MOZ_ASSERT(lhs != nullptr); + MOZ_ASSERT(name != nullptr); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_OPTDOT); + MOZ_ASSERT_IF(match, node.isArity(PN_NAME)); + return match; + } +}; + +class PropertyByValueBase : public ParseNode { public: - PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end) - : ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end)) + PropertyByValueBase(ParseNodeKind kind, ParseNode* lhs, ParseNode* propExpr, + uint32_t begin, uint32_t end) + : ParseNode(kind, JSOP_NOP, PN_BINARY, TokenPos(begin, end)) { pn_u.binary.left = lhs; pn_u.binary.right = propExpr; } static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_ELEM) || + node.isKind(PNK_OPTELEM); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + return match; + } +}; + +class PropertyByValue : public PropertyByValueBase { + public: + PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, + uint32_t end) + : PropertyByValueBase(PNK_ELEM, lhs, propExpr, begin, end) {} + + static bool test(const ParseNode& node) { bool match = node.isKind(PNK_ELEM); MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); return match; @@ -1235,6 +1316,19 @@ class PropertyByValue : public ParseNode } }; +class OptionalPropertyByValue : public PropertyByValueBase { + public: + OptionalPropertyByValue(ParseNode* lhs, ParseNode* propExpr, + uint32_t begin, uint32_t end) + : PropertyByValueBase(PNK_OPTELEM, lhs, propExpr, begin, end) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_OPTELEM); + MOZ_ASSERT_IF(match, node.isArity(PN_BINARY)); + return match; + } +}; + /* * A CallSiteNode represents the implicit call site object argument in a TaggedTemplate. */ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 38bd77ead..5202b7154 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7154,7 +7154,7 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (hasHeritage) { if (!tokenStream.getToken(&tt)) return null(); - classHeritage = memberExpr(yieldHandling, TripledotProhibited, tt); + classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt); if (!classHeritage) return null(); } @@ -8363,6 +8363,108 @@ Parser<ParseHandler>::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kin template <typename ParseHandler> typename ParseHandler::Node +Parser<ParseHandler>::optionalExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, bool allowCallSyntax /* = true */, + PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) +{ + JS_CHECK_RECURSION(context, return null()); + + uint32_t begin = pos().begin; + + Node lhs = memberExpr(yieldHandling, tripledotHandling, tt, + /* allowCallSyntax = */ true, possibleError, invoked); + + if (!lhs) { + return null(); + } + + // Note: TokenStream::None is equivalent to TokenStream::SlashIsDiv + // in current Mozilla code, see bug 1539821. + if (!tokenStream.peekToken(&tt, TokenStream::None)) { + return null(); + } + + if (tt != TOK_OPTCHAIN) { + return lhs; + } + + while (true) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TOK_EOF) { + break; + } + + Node nextMember; + if (tt == TOK_OPTCHAIN) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (TokenKindIsPossibleIdentifierName(tt)) { + nextMember = memberPropertyAccess(lhs, OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else if (tt == TOK_LB) { + nextMember = memberElemAccess(lhs, yieldHandling, + OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else if (tt == TOK_LP) { + nextMember = memberCall(tt, lhs, yieldHandling, possibleError, + OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else { + error(JSMSG_NAME_AFTER_DOT); + return null(); + } + } else if (tt == TOK_DOT) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (TokenKindIsPossibleIdentifierName(tt)) { + nextMember = memberPropertyAccess(lhs); + if (!nextMember) { + return null(); + } + } else { + error(JSMSG_NAME_AFTER_DOT); + return null(); + } + } else if (tt == TOK_LB) { + nextMember = memberElemAccess(lhs, yieldHandling); + if (!nextMember) { + return null(); + } + } else if (allowCallSyntax && tt == TOK_LP) { + nextMember = memberCall(tt, lhs, yieldHandling, possibleError); + if (!nextMember) { + return null(); + } + } else if (tt == TOK_TEMPLATE_HEAD || tt == TOK_NO_SUBS_TEMPLATE) { + error(JSMSG_BAD_OPTIONAL_TEMPLATE); + return null(); + } else { + tokenStream.ungetToken(); + break; + } + + MOZ_ASSERT(nextMember); + lhs = nextMember; + } + + return handler.newOptionalChain(begin, lhs); +} + +template <typename ParseHandler> +typename ParseHandler::Node Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling, PossibleError* possibleError /* = nullptr */, InvokedPrediction invoked /* = PredictUninvoked */) @@ -8412,7 +8514,7 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t return null(); uint32_t operandOffset = pos().begin; - Node operand = memberExpr(yieldHandling, TripledotProhibited, tt2); + Node operand = optionalExpr(yieldHandling, TripledotProhibited, tt2); if (!operand || !checkIncDecOperand(operand, operandOffset)) return null(); @@ -8454,8 +8556,9 @@ Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling t MOZ_FALLTHROUGH; default: { - Node expr = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true, - possibleError, invoked); + Node expr = optionalExpr(yieldHandling, tripledotHandling, tt, + /* allowCallSyntax = */ true, + possibleError, invoked); if (!expr) return null(); @@ -8904,6 +9007,18 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling handler.addList(lhs, ctorExpr); + // If we have encountered an optional chain, in the form of `new + // ClassName?.()` then we need to throw, as this is disallowed + // by the spec. + bool optionalToken; + if (!tokenStream.matchToken(&optionalToken, TOK_OPTCHAIN)) { + return null(); + } + if (optionalToken) { + errorAt(newBegin, JSMSG_BAD_NEW_OPTIONAL); + return null(); + } + bool matched; if (!tokenStream.matchToken(&matched, TOK_LP)) return null(); @@ -8941,12 +9056,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling if (!tokenStream.getToken(&tt)) return null(); if (TokenKindIsPossibleIdentifierName(tt)) { - PropertyName* field = tokenStream.currentName(); - if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) { - error(JSMSG_BAD_SUPERPROP, "property"); - return null(); - } - nextMember = handler.newPropertyAccess(lhs, field, pos().end); + nextMember = memberPropertyAccess(lhs); if (!nextMember) return null(); } else { @@ -8954,17 +9064,7 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling return null(); } } else if (tt == TOK_LB) { - Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited); - if (!propExpr) - return null(); - - MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); - - if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) { - error(JSMSG_BAD_SUPERPROP, "member"); - return null(); - } - nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end); + nextMember = memberElemAccess(lhs, yieldHandling); if (!nextMember) return null(); } else if ((allowCallSyntax && tt == TOK_LP) || @@ -9000,82 +9100,17 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling if (!thisName) return null(); + // XXX This was refactored out into memberSuperCall as part of + // bug 1566143, but was not used elsewhere aside from here, so + // I opted not to move this into a separate function. However, + // I'm unsure if this will be needed eventually in the future. nextMember = handler.newSetThis(thisName, nextMember); if (!nextMember) return null(); } else { - if (options().selfHostingMode && handler.isPropertyAccess(lhs)) { - error(JSMSG_SELFHOSTED_METHOD_CALL); - return null(); - } - - nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate(); + nextMember = memberCall(tt, lhs, yieldHandling, possibleError); if (!nextMember) return null(); - - JSOp op = JSOP_CALL; - bool maybeAsyncArrow = false; - if (PropertyName* prop = handler.maybeDottedProperty(lhs)) { - // Use the JSOP_FUN{APPLY,CALL} optimizations given the - // right syntax. - if (prop == context->names().apply) { - op = JSOP_FUNAPPLY; - if (pc->isFunctionBox()) { - pc->functionBox()->usesApply = true; - } - } else if (prop == context->names().call) { - op = JSOP_FUNCALL; - } - } else if (tt == TOK_LP) { - if (handler.isAsyncKeyword(lhs, context)) { - // |async (| can be the start of an async arrow - // function, so we need to defer reporting possible - // errors from destructuring syntax. To give better - // error messages, we only allow the AsyncArrowHead - // part of the CoverCallExpressionAndAsyncArrowHead - // syntax when the initial name is "async". - maybeAsyncArrow = true; - } else if (handler.isEvalAnyParentheses(lhs, context)) { - // Select the right EVAL op and flag pc as having a - // direct eval. - op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL; - pc->sc()->setBindingsAccessedDynamically(); - pc->sc()->setHasDirectEval(); - - // In non-strict mode code, direct calls to eval can - // add variables to the call object. - if (pc->isFunctionBox() && !pc->sc()->strict()) - pc->functionBox()->setHasExtensibleScope(); - - // If we're in a method, mark the method as requiring - // support for 'super', since direct eval code can use - // it. (If we're not in a method, that's fine, so - // ignore the return value.) - checkAndMarkSuperScope(); - } - } - - handler.setBeginPosition(nextMember, lhs); - handler.addList(nextMember, lhs); - - if (tt == TOK_LP) { - bool isSpread = false; - PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr; - if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError)) - return null(); - if (isSpread) { - if (op == JSOP_EVAL) - op = JSOP_SPREADEVAL; - else if (op == JSOP_STRICTEVAL) - op = JSOP_STRICTSPREADEVAL; - else - op = JSOP_SPREADCALL; - } - } else { - if (!taggedTemplate(yieldHandling, nextMember, tt)) - return null(); - } - handler.setOp(nextMember, op); } } else { tokenStream.ungetToken(); @@ -9109,6 +9144,158 @@ Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos) return handler.newName(name, pos, context); } +template <class ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::memberPropertyAccess( + Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) +{ + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tokenStream.currentToken().type)); + PropertyName* field = tokenStream.currentName(); + if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) { + error(JSMSG_BAD_SUPERPROP, "property"); + return null(); + } + if (optionalKind == OptionalKind::Optional) { + MOZ_ASSERT(!handler.isSuperBase(lhs)); + return handler.newOptionalPropertyAccess(lhs, field, pos().end); + } + return handler.newPropertyAccess(lhs, field, pos().end); +} + +template <class ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::memberElemAccess( + Node lhs, YieldHandling yieldHandling, + OptionalKind optionalKind /* = OptionalKind::NonOptional */) +{ + MOZ_ASSERT(tokenStream.currentToken().type == TOK_LB); + Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!propExpr) { + return null(); + } + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); + + if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) { + error(JSMSG_BAD_SUPERPROP, "member"); + return null(); + } + + if (optionalKind == OptionalKind::Optional) { + MOZ_ASSERT(!handler.isSuperBase(lhs)); + return handler.newOptionalPropertyByValue(lhs, propExpr, pos().end); + } + return handler.newPropertyByValue(lhs, propExpr, pos().end); +} + +template <class ParseHandler> +typename ParseHandler::Node +Parser<ParseHandler>::memberCall( + TokenKind tt, Node lhs, YieldHandling yieldHandling, + PossibleError* possibleError /* = nullptr */, + OptionalKind optionalKind /* = OptionalKind::NonOptional */) +{ + if (options().selfHostingMode && (handler.isPropertyAccess(lhs) || + handler.isOptionalPropertyAccess(lhs))) { + error(JSMSG_SELFHOSTED_METHOD_CALL); + return null(); + } + + MOZ_ASSERT(tt == TOK_LP || + tt == TOK_TEMPLATE_HEAD || + tt == TOK_NO_SUBS_TEMPLATE, + "Unexpected token kind for member call"); + + Node nextMember; + if (tt == TOK_LP) { + if (optionalKind == OptionalKind::Optional) { + nextMember = handler.newOptionalCall(); + } else { + nextMember = handler.newCall(); + } + } else { + nextMember = handler.newTaggedTemplate(); + } + + JSOp op = JSOP_CALL; + bool maybeAsyncArrow = false; + if (PropertyName* prop = handler.maybeDottedProperty(lhs)) { + // Use the JSOP_FUN{APPLY,CALL} optimizations given the + // right syntax. + if (prop == context->names().apply) { + op = JSOP_FUNAPPLY; + if (pc->isFunctionBox()) { + pc->functionBox()->usesApply = true; + } + } else if (prop == context->names().call) { + op = JSOP_FUNCALL; + } + } else if (tt == TOK_LP && optionalKind == OptionalKind::NonOptional) { + if (handler.isAsyncKeyword(lhs, context)) { + // |async (| can be the start of an async arrow + // function, so we need to defer reporting possible + // errors from destructuring syntax. To give better + // error messages, we only allow the AsyncArrowHead + // part of the CoverCallExpressionAndAsyncArrowHead + // syntax when the initial name is "async". + maybeAsyncArrow = true; + } else if (handler.isEvalAnyParentheses(lhs, context)) { + // Select the right EVAL op and flag pc as having a + // direct eval. + op = pc->sc()->strict() ? JSOP_STRICTEVAL : JSOP_EVAL; + pc->sc()->setBindingsAccessedDynamically(); + pc->sc()->setHasDirectEval(); + + // In non-strict mode code, direct calls to eval can + // add variables to the call object. + if (pc->isFunctionBox() && !pc->sc()->strict()) { + pc->functionBox()->setHasExtensibleScope(); + } + + // If we're in a method, mark the method as requiring + // support for 'super', since direct eval code can use + // it. (If we're not in a method, that's fine, so + // ignore the return value.) + checkAndMarkSuperScope(); + } + } + + handler.setBeginPosition(nextMember, lhs); + handler.addList(nextMember, lhs); + + if (tt == TOK_LP) { + bool isSpread = false; + PossibleError* asyncPossibleError = maybeAsyncArrow ? + possibleError : + nullptr; + + if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError)) { + return null(); + } + + if (isSpread) { + if (op == JSOP_EVAL) { + op = JSOP_SPREADEVAL; + } else if (op == JSOP_STRICTEVAL) { + op = JSOP_STRICTSPREADEVAL; + } else { + op = JSOP_SPREADCALL; + } + } + } else { + if (!taggedTemplate(yieldHandling, nextMember, tt)) { + return null(); + } + if (optionalKind == OptionalKind::Optional) { + error(JSMSG_BAD_OPTIONAL_TEMPLATE); + return null(); + } + } + handler.setOp(nextMember, op); + + return nextMember; +} + template <typename ParseHandler> bool Parser<ParseHandler>::checkLabelOrIdentifierReference(HandlePropertyName ident, diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 0e2f28fe8..97f6917bd 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1321,6 +1321,11 @@ class Parser final : public ParserBase, private JS::AutoGCRooter Node unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling, PossibleError* possibleError = nullptr, InvokedPrediction invoked = PredictUninvoked); + Node optionalExpr(YieldHandling yieldHandling, + TripledotHandling tripledotHandling, TokenKind tt, + bool allowCallSyntax = true, + PossibleError* possibleError = nullptr, + InvokedPrediction invoked = PredictUninvoked); Node memberExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling, TokenKind tt, bool allowCallSyntax = true, PossibleError* possibleError = nullptr, @@ -1532,6 +1537,18 @@ class Parser final : public ParserBase, private JS::AutoGCRooter JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom); bool asmJS(Node list); + + enum class OptionalKind { + NonOptional = 0, + Optional, + }; + Node memberPropertyAccess( + Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional); + Node memberElemAccess(Node lhs, YieldHandling yieldHandling, + OptionalKind optionalKind = OptionalKind::NonOptional); + Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling, + PossibleError* possibleError, + OptionalKind optionalKind = OptionalKind::NonOptional); }; template <typename ParseHandler> diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h index 3d2a1bc1f..3951f5564 100644 --- a/js/src/frontend/SourceNotes.h +++ b/js/src/frontend/SourceNotes.h @@ -49,6 +49,7 @@ namespace js { M(SRC_BREAK, "break", 0) /* JSOP_GOTO is a break. */ \ M(SRC_BREAK2LABEL, "break2label", 0) /* JSOP_GOTO for 'break label'. */ \ M(SRC_SWITCHBREAK, "switchbreak", 0) /* JSOP_GOTO is a break in a switch. */ \ + M(SRC_OPTCHAIN, "optchain", 0) /* JSOP_GOTO for optional chains. */ \ M(SRC_TABLESWITCH, "tableswitch", 1) /* JSOP_TABLESWITCH; offset points to end of switch. */ \ M(SRC_CONDSWITCH, "condswitch", 2) /* JSOP_CONDSWITCH; 1st offset points to end of switch, \ 2nd points to first JSOP_CASE. */ \ @@ -63,7 +64,6 @@ namespace js { M(SRC_COLSPAN, "colspan", 1) /* Number of columns this opcode spans. */ \ M(SRC_NEWLINE, "newline", 0) /* Bytecode follows a source newline. */ \ M(SRC_SETLINE, "setline", 1) /* A file-absolute source line number note. */ \ - M(SRC_UNUSED21, "unused21", 0) /* Unused. */ \ M(SRC_UNUSED22, "unused22", 0) /* Unused. */ \ M(SRC_UNUSED23, "unused23", 0) /* Unused. */ \ M(SRC_XDELTA, "xdelta", 0) /* 24-31 are for extended delta notes. */ diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 4fef3584c..85c806116 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -58,6 +58,7 @@ class SyntaxParseHandler // in code not actually executed (or at least not executed enough to be // noticed). NodeFunctionCall, + NodeOptionalFunctionCall, // Nodes representing *parenthesized* IsValidSimpleAssignmentTarget // nodes. We can't simply treat all such parenthesized nodes @@ -78,7 +79,9 @@ class SyntaxParseHandler NodeParenthesizedName, NodeDottedProperty, + NodeOptionalDottedProperty, NodeElement, + NodeOptionalElement, // Destructuring target patterns can't be parenthesized: |([a]) = [3];| // must be a syntax error. (We can't use NodeGeneric instead of these @@ -146,6 +149,10 @@ class SyntaxParseHandler return node == NodeDottedProperty || node == NodeElement; } + bool isOptionalPropertyAccess(Node node) { + return node == NodeOptionalDottedProperty || node == NodeOptionalElement; + } + bool isFunctionCall(Node node) { // Note: super() is a special form, *not* a function call. return node == NodeFunctionCall; @@ -283,6 +290,7 @@ class SyntaxParseHandler void addArrayElement(Node literal, Node element) { } Node newCall() { return NodeFunctionCall; } + Node newOptionalCall() { return NodeOptionalFunctionCall; } Node newTaggedTemplate() { return NodeGeneric; } Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } @@ -303,6 +311,7 @@ class SyntaxParseHandler Node newYieldExpression(uint32_t begin, Node value) { return NodeGeneric; } Node newYieldStarExpression(uint32_t begin, Node value) { return NodeGeneric; } Node newAwaitExpression(uint32_t begin, Node value) { return NodeGeneric; } + Node newOptionalChain(uint32_t begin, Node value) { return NodeGeneric; } // Statements @@ -353,8 +362,15 @@ class SyntaxParseHandler return NodeDottedProperty; } + Node newOptionalPropertyAccess(Node pn, PropertyName* name, uint32_t end) { + lastAtom = name; + return NodeOptionalDottedProperty; + } + Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; } + Node newOptionalPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeOptionalElement; } + MOZ_MUST_USE bool addCatchBlock(Node catchList, Node letBlock, Node catchName, Node catchGuard, Node catchBody) { return true; } @@ -471,7 +487,8 @@ class SyntaxParseHandler list == NodeUnparenthesizedCommaExpr || list == NodeVarDeclaration || list == NodeLexicalDeclaration || - list == NodeFunctionCall); + list == NodeFunctionCall || + list == NodeOptionalFunctionCall); } Node newAssignment(ParseNodeKind kind, Node lhs, Node rhs, JSOp op) { @@ -590,7 +607,7 @@ class SyntaxParseHandler // |this|. It's not really eligible for the funapply/funcall // optimizations as they're currently implemented (assuming a single // value is used for both retrieval and |this|). - if (node != NodeDottedProperty) + if (node != NodeDottedProperty && node != NodeOptionalDottedProperty) return nullptr; return lastAtom->asPropertyName(); } diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index f4e1afa32..27da8ecf3 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -63,6 +63,7 @@ macro(DEC, "'--'") /* decrement */ \ macro(DOT, "'.'") /* member operator */ \ macro(TRIPLEDOT, "'...'") /* rest arguments and spread operator */ \ + macro(OPTCHAIN, "'?.'") \ macro(LB, "'['") \ macro(RB, "']'") \ macro(LC, "'{'") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 083bcd504..8f9e206d9 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1285,7 +1285,7 @@ static const uint8_t firstCharKinds[] = { /* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String, /* 40+ */ TOK_LP, TOK_RP, _______, _______, T_COMMA,_______, _______, _______,BasePrefix, Dec, /* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON,TOK_SEMI, -/* 60+ */ _______, _______, _______,TOK_HOOK, _______, Ident, Ident, Ident, Ident, Ident, +/* 60+ */ _______, _______, _______, _______, _______, Ident, Ident, Ident, Ident, Ident, /* 70+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, /* 80+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, /* 90+ */ Ident, TOK_LB, _______, TOK_RB, _______, Ident, Templat, Ident, Ident, Ident, @@ -1796,6 +1796,25 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND; goto out; + case '?': + if (matchChar('.')) { + c = getCharIgnoreEOL(); + if (JS7_ISDEC(c)) { + // if the code unit is followed by a number, for example it has + // the following form `<...> ?.5 <..> then it should be treated + // as a ternary rather than as an optional chain + tp->type = TOK_HOOK; + ungetCharIgnoreEOL(c); + ungetChar('.'); + } else { + ungetCharIgnoreEOL(c); + tp->type = TOK_OPTCHAIN; + } + } else { + tp->type = TOK_HOOK; + } + goto out; + case '!': if (matchChar('=')) tp->type = matchChar('=') ? TOK_STRICTNE : TOK_NE; |