diff options
author | Moonchild <moonchild@palemoon.org> | 2023-08-30 09:36:31 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2023-08-30 09:36:31 +0000 |
commit | e112df88498ba2c85dfbaf26d7db0d8c6c3b5448 (patch) | |
tree | f460b2ef25696ff9bd1a4bae768067de3d2e32a9 | |
parent | 45ec6b3a8663995e2f0049436add2dfbe214071e (diff) | |
parent | 8b9d69f912cc52fb8a4b007041922bad8e131e82 (diff) | |
download | uxp-e112df88498ba2c85dfbaf26d7db0d8c6c3b5448.tar.gz |
Merge pull request 'Implement ergonomic brand check' (#2300) from martok/UXP-contrib:mr/ergo-brand-check into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2300
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 19 | ||||
-rw-r--r-- | js/src/frontend/FullParseHandler.h | 4 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 4 | ||||
-rw-r--r-- | js/src/frontend/Parser.cpp | 24 | ||||
-rw-r--r-- | js/src/frontend/SyntaxParseHandler.h | 12 | ||||
-rw-r--r-- | js/src/js.msg | 1 |
6 files changed, 59 insertions, 5 deletions
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index cb249c254c..462edfd91e 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7754,10 +7754,23 @@ bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { // Left-associative operator chain. - if (!emitTree(node->head())) - return false; JSOp op = node->getOp(); - ParseNode* nextExpr = node->head()->pn_next; + ParseNode* headExpr = node->head(); + if (op == JSOP_IN && headExpr->isKind(PNK_NAME) && headExpr->as<NameNode>().isPrivateName()) { + // {Goanna} The only way a "naked" private name can show up as the leftmost side of an in-chain + // is from an ergonomic brand check (`this.#x in ...` would be a PNK_DOT child node). + // Instead of going through the emitTree machinery, we pretend that this identifier + // reference is actually a string, which allows us to use the JSOP_IN interpreter routines. + // This erroneously doesn't call updateLineNumberNotes, but this is not a big issue: + // the begin pos is correct as we're on the start of the current tree, the end is on the + // same line anyway. + if (!emitAtomOp(headExpr->as<NameNode>().atom(), JSOP_STRING)) + return false; + } else { + if (!emitTree(headExpr)) + return false; + } + ParseNode* nextExpr = headExpr->pn_next; do { if (!emitTree(nextExpr)) return false; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index e27b9f540a..d2c8bbc2df 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -965,6 +965,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node->isKind(PNK_NAME); } + bool isPrivateName(Node node) { + return node->isKind(PNK_NAME) && node->as<NameNode>().isPrivateName(); + } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { return node->isKind(PNK_NAME) && node->as<NameNode>().atom() == cx->names().arguments; } diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index b1fcab0735..439a6d8937 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -953,6 +953,10 @@ class NameNode : public ParseNode JSAtom* atom() const { return pn_u.name.atom; } + + bool isPrivateName() const { + return atom()->asPropertyName()->latin1OrTwoByteChar(0) == '#'; + } ParseNode* initializer() const { return pn_u.name.initOrStmt; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 9da893f94f..4e3181e06b 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -8949,6 +8949,15 @@ Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling if (!tokenStream.getToken(&tok)) return null(); + // Ensure that if we have a private name lhs we are legally constructing a + // `#x in obj` expression: + if (handler.isPrivateName(pn)) { + if (tok != TOK_IN) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + ParseNodeKind pnk; if (tok == TOK_IN ? inHandling == InAllowed : TokenKindIsBinaryOp(tok)) { // We're definitely not in a destructuring context, so report any @@ -8985,7 +8994,20 @@ Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling // If we have not detected a mixing error at this point, record that // we have an unparenthesized expression, in case we have one later. unparenthesizedExpression = EnforcedParentheses::CoalesceExpr; - break; + break; + case TOK_IN: + // if the LHS is a private name, and the operator is In, + // ensure we're construcing an ergonomic brand check of + // '#x in y', rather than having a higher precedence operator + // like + cause a different reduction, such as + // 1 + #x in y. + if (handler.isPrivateName(pn)) { + if (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(PNK_IN)) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + break; default: // Do nothing in other cases. break; diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 818530cfe9..80ca13ce45 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -104,6 +104,9 @@ class SyntaxParseHandler // Node representing the "async" name, which may actually be a // contextual keyword. NodePotentialAsyncKeyword, + + // Node representing a private name. Handled mostly like NodeUnparenthesizedName. + NodePrivateName, // Valuable for recognizing potential destructuring patterns. NodeUnparenthesizedArray, @@ -212,6 +215,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return NodePotentialAsyncKeyword; if (name == cx->names().eval) return NodeUnparenthesizedEvalName; + if (name->length() >= 1 && name->latin1OrTwoByteChar(0) == '#') + return NodePrivateName; return NodeUnparenthesizedName; } @@ -614,7 +619,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node == NodeUnparenthesizedArgumentsName || node == NodeUnparenthesizedEvalName || node == NodeUnparenthesizedName || - node == NodePotentialAsyncKeyword; + node == NodePotentialAsyncKeyword || + node == NodePrivateName; } bool isNameAnyParentheses(Node node) { @@ -625,6 +631,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) node == NodeParenthesizedName; } + bool isPrivateName(Node node) { + return node == NodePrivateName; + } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { return node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName; } diff --git a/js/src/js.msg b/js/src/js.msg index 1cb571d189..340cfd6ad2 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -364,6 +364,7 @@ MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot b 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") +MSG_DEF(JSMSG_ILLEGAL_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "private names aren't valid in this context") // asm.js MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") |