summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2023-08-30 09:36:31 +0000
committerMoonchild <moonchild@palemoon.org>2023-08-30 09:36:31 +0000
commite112df88498ba2c85dfbaf26d7db0d8c6c3b5448 (patch)
treef460b2ef25696ff9bd1a4bae768067de3d2e32a9
parent45ec6b3a8663995e2f0049436add2dfbe214071e (diff)
parent8b9d69f912cc52fb8a4b007041922bad8e131e82 (diff)
downloaduxp-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.cpp19
-rw-r--r--js/src/frontend/FullParseHandler.h4
-rw-r--r--js/src/frontend/ParseNode.h4
-rw-r--r--js/src/frontend/Parser.cpp24
-rw-r--r--js/src/frontend/SyntaxParseHandler.h12
-rw-r--r--js/src/js.msg1
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}")