diff options
author | Martok <martok@martoks-place.de> | 2023-04-26 15:16:33 +0200 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-05-01 17:16:22 +0200 |
commit | 3d38507c23f3dc7b0cb8b69d54d41752fb8be08c (patch) | |
tree | 3ebce8f6772b48b37a93a9b903f2d6cc31b0f824 | |
parent | 12626eeba378d8f528b73d230ec1aec5752e6f22 (diff) | |
download | uxp-3d38507c23f3dc7b0cb8b69d54d41752fb8be08c.tar.gz |
Issue #2097 - Implement logical assignment operators
Based-on: 1629106/1, 1684020
-rw-r--r-- | js/src/builtin/ReflectParse.cpp | 14 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 244 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 5 | ||||
-rw-r--r-- | js/src/frontend/FoldConstants.cpp | 6 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.cpp | 3 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 9 | ||||
-rw-r--r-- | js/src/frontend/Parser.cpp | 14 | ||||
-rw-r--r-- | js/src/frontend/TokenKind.h | 3 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.cpp | 10 |
9 files changed, 302 insertions, 6 deletions
diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index b40c5fb408..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[] = { @@ -1974,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; } @@ -3103,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/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index eb25903911..1e6390f5f1 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -518,6 +518,17 @@ BytecodeEmitter::emitPopN(unsigned 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) { return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); @@ -1221,6 +1232,9 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) 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: @@ -3978,6 +3992,225 @@ BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, JSOp compoundOp, } 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<NameOpEmitter> noe; + Maybe<PropOpEmitter> poe; + Maybe<ElemOpEmitter> 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<NameNode>().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<PropertyAccess>(); + 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<UnaryNode>(); + 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<PropertyByValue>(); + 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. + // <https://github.com/tc39/proposal-logical-assignment/issues/23> + 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<PropertyAccess>(); + + 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, NewObjectKind newKind) @@ -9074,6 +9307,17 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: break; } + case PNK_COALESCEASSIGN: + case PNK_ORASSIGN: + case PNK_ANDASSIGN: { + BinaryNode* assignNode = &pn->as<BinaryNode>(); + if (!emitShortCircuitAssignment(assignNode->getKind(), assignNode->getOp(), + assignNode->left(), assignNode->right())) { + return false; + } + break; + } + case PNK_CONDITIONAL: if (!emitConditionalExpression(pn->as<ConditionalExpression>(), valueUsage)) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 7521e236dc..732a1f24f8 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -457,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); @@ -701,6 +704,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); 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); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 813900a23f..9fb963f63e 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -355,6 +355,9 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) 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: @@ -1884,6 +1887,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo 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: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index a529c93d14..7ad470865f 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -262,6 +262,9 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) 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/ParseNode.h b/js/src/frontend/ParseNode.h index 471f42f905..46977ee253 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -176,6 +176,9 @@ class ObjectBox; F(ASSIGN) \ F(ADDASSIGN) \ F(SUBASSIGN) \ + F(COALESCEASSIGN) \ + F(ORASSIGN) \ + F(ANDASSIGN) \ F(BITORASSIGN) \ F(BITXORASSIGN) \ F(BITANDASSIGN) \ @@ -395,8 +398,10 @@ IsTypeofKind(ParseNodeKind kind) * 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 diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index a088949c8b..a33b3d0d90 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -9150,6 +9150,9 @@ Parser<ParseHandler>::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; @@ -9279,6 +9282,17 @@ Parser<ParseHandler>::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(); diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index f6f947f608..232e373dcf 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -219,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, "'&='") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index d1e586afc2..a201e42a52 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1876,7 +1876,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) case '|': if (matchChar('|')) - tp->type = TOK_OR; + tp->type = matchChar('=') ? TOK_ORASSIGN : TOK_OR; else tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR; goto out; @@ -1887,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; @@ -1906,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; |