Duplicate __proto__ key should be allowed in object patterns (#10987)

* refactor: replace refShorthandDefaultPos by refExpressionErrors

* fix: duplicate __proto__ keys should be allowed in patterns

* docs: add comments for ExpressionErrors.doubleProto [ci-skip]

* test: add more test for coverage
This commit is contained in:
Huáng Jùnliàng 2020-01-14 22:53:45 -05:00 committed by GitHub
parent a0a9c64a47
commit 9df70b4505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 820 additions and 96 deletions

View File

@ -41,6 +41,7 @@ import {
SCOPE_PROGRAM, SCOPE_PROGRAM,
SCOPE_ASYNC, SCOPE_ASYNC,
} from "../util/scopeflags"; } from "../util/scopeflags";
import { ExpressionErrors } from "./util";
export default class ExpressionParser extends LValParser { export default class ExpressionParser extends LValParser {
// Forward-declaration: defined in statement.js // Forward-declaration: defined in statement.js
@ -69,7 +70,8 @@ export default class ExpressionParser extends LValParser {
checkDuplicatedProto( checkDuplicatedProto(
prop: N.ObjectMember | N.SpreadElement, prop: N.ObjectMember | N.SpreadElement,
protoRef: { used: boolean, start?: number }, protoRef: { used: boolean },
refExpressionErrors: ?ExpressionErrors,
): void { ): void {
if ( if (
prop.type === "SpreadElement" || prop.type === "SpreadElement" ||
@ -87,8 +89,12 @@ export default class ExpressionParser extends LValParser {
if (name === "__proto__") { if (name === "__proto__") {
// Store the first redefinition's position // Store the first redefinition's position
if (protoRef.used && !protoRef.start) { if (protoRef.used) {
protoRef.start = key.start; if (refExpressionErrors && refExpressionErrors.doubleProto === -1) {
refExpressionErrors.doubleProto = key.start;
} else {
this.raise(key.start, "Redefinition of __proto__ property");
}
} }
protoRef.used = true; protoRef.used = true;
@ -127,17 +133,18 @@ export default class ExpressionParser extends LValParser {
// and object pattern might appear (so it's possible to raise // and object pattern might appear (so it's possible to raise
// delayed syntax error at correct position). // delayed syntax error at correct position).
parseExpression(noIn?: boolean, refShorthandDefaultPos?: Pos): N.Expression { parseExpression(
noIn?: boolean,
refExpressionErrors?: ExpressionErrors,
): N.Expression {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
const expr = this.parseMaybeAssign(noIn, refShorthandDefaultPos); const expr = this.parseMaybeAssign(noIn, refExpressionErrors);
if (this.match(tt.comma)) { if (this.match(tt.comma)) {
const node = this.startNodeAt(startPos, startLoc); const node = this.startNodeAt(startPos, startLoc);
node.expressions = [expr]; node.expressions = [expr];
while (this.eat(tt.comma)) { while (this.eat(tt.comma)) {
node.expressions.push( node.expressions.push(this.parseMaybeAssign(noIn, refExpressionErrors));
this.parseMaybeAssign(noIn, refShorthandDefaultPos),
);
} }
this.toReferencedList(node.expressions); this.toReferencedList(node.expressions);
return this.finishNode(node, "SequenceExpression"); return this.finishNode(node, "SequenceExpression");
@ -150,7 +157,7 @@ export default class ExpressionParser extends LValParser {
parseMaybeAssign( parseMaybeAssign(
noIn?: ?boolean, noIn?: ?boolean,
refShorthandDefaultPos?: ?Pos, refExpressionErrors?: ?ExpressionErrors,
afterLeftParse?: Function, afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos, refNeedsArrowPos?: ?Pos,
): N.Expression { ): N.Expression {
@ -170,12 +177,12 @@ export default class ExpressionParser extends LValParser {
} }
} }
let failOnShorthandAssign; let ownExpressionErrors;
if (refShorthandDefaultPos) { if (refExpressionErrors) {
failOnShorthandAssign = false; ownExpressionErrors = false;
} else { } else {
refShorthandDefaultPos = { start: 0 }; refExpressionErrors = new ExpressionErrors();
failOnShorthandAssign = true; ownExpressionErrors = true;
} }
if (this.match(tt.parenL) || this.match(tt.name)) { if (this.match(tt.parenL) || this.match(tt.name)) {
@ -184,7 +191,7 @@ export default class ExpressionParser extends LValParser {
let left = this.parseMaybeConditional( let left = this.parseMaybeConditional(
noIn, noIn,
refShorthandDefaultPos, refExpressionErrors,
refNeedsArrowPos, refNeedsArrowPos,
); );
if (afterLeftParse) { if (afterLeftParse) {
@ -201,12 +208,15 @@ export default class ExpressionParser extends LValParser {
if (operator === "||=" || operator === "&&=") { if (operator === "||=" || operator === "&&=") {
this.expectPlugin("logicalAssignment"); this.expectPlugin("logicalAssignment");
} }
node.left = this.match(tt.eq) if (this.match(tt.eq)) {
? this.toAssignable(left, undefined, "assignment expression") node.left = this.toAssignable(left, undefined, "assignment expression");
: left; refExpressionErrors.doubleProto = -1; // reset because double __proto__ is valid in assignment expression
} else {
node.left = left;
}
if (refShorthandDefaultPos.start >= node.left.start) { if (refExpressionErrors.shorthandAssign >= node.left.start) {
refShorthandDefaultPos.start = 0; // reset because shorthand default was used correctly refExpressionErrors.shorthandAssign = -1; // reset because shorthand default was used correctly
} }
this.checkLVal(left, undefined, undefined, "assignment expression"); this.checkLVal(left, undefined, undefined, "assignment expression");
@ -214,8 +224,8 @@ export default class ExpressionParser extends LValParser {
this.next(); this.next();
node.right = this.parseMaybeAssign(noIn); node.right = this.parseMaybeAssign(noIn);
return this.finishNode(node, "AssignmentExpression"); return this.finishNode(node, "AssignmentExpression");
} else if (failOnShorthandAssign && refShorthandDefaultPos.start) { } else if (ownExpressionErrors) {
this.unexpected(refShorthandDefaultPos.start); this.checkExpressionErrors(refExpressionErrors, true);
} }
return left; return left;
@ -225,13 +235,13 @@ export default class ExpressionParser extends LValParser {
parseMaybeConditional( parseMaybeConditional(
noIn: ?boolean, noIn: ?boolean,
refShorthandDefaultPos: Pos, refExpressionErrors: ExpressionErrors,
refNeedsArrowPos?: ?Pos, refNeedsArrowPos?: ?Pos,
): N.Expression { ): N.Expression {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
const potentialArrowAt = this.state.potentialArrowAt; const potentialArrowAt = this.state.potentialArrowAt;
const expr = this.parseExprOps(noIn, refShorthandDefaultPos); const expr = this.parseExprOps(noIn, refExpressionErrors);
if ( if (
expr.type === "ArrowFunctionExpression" && expr.type === "ArrowFunctionExpression" &&
@ -239,7 +249,7 @@ export default class ExpressionParser extends LValParser {
) { ) {
return expr; return expr;
} }
if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; if (this.checkExpressionErrors(refExpressionErrors, false)) return expr;
return this.parseConditional( return this.parseConditional(
expr, expr,
@ -272,11 +282,14 @@ export default class ExpressionParser extends LValParser {
// Start the precedence parser. // Start the precedence parser.
parseExprOps(noIn: ?boolean, refShorthandDefaultPos: Pos): N.Expression { parseExprOps(
noIn: ?boolean,
refExpressionErrors: ExpressionErrors,
): N.Expression {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
const potentialArrowAt = this.state.potentialArrowAt; const potentialArrowAt = this.state.potentialArrowAt;
const expr = this.parseMaybeUnary(refShorthandDefaultPos); const expr = this.parseMaybeUnary(refExpressionErrors);
if ( if (
expr.type === "ArrowFunctionExpression" && expr.type === "ArrowFunctionExpression" &&
@ -284,7 +297,7 @@ export default class ExpressionParser extends LValParser {
) { ) {
return expr; return expr;
} }
if (refShorthandDefaultPos && refShorthandDefaultPos.start) { if (this.checkExpressionErrors(refExpressionErrors, false)) {
return expr; return expr;
} }
@ -463,7 +476,7 @@ export default class ExpressionParser extends LValParser {
// Parse unary operators, both prefix and postfix. // Parse unary operators, both prefix and postfix.
parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression { parseMaybeUnary(refExpressionErrors: ?ExpressionErrors): N.Expression {
if (this.isContextual("await") && this.isAwaitAllowed()) { if (this.isContextual("await") && this.isAwaitAllowed()) {
return this.parseAwait(); return this.parseAwait();
} else if (this.state.type.prefix) { } else if (this.state.type.prefix) {
@ -479,9 +492,7 @@ export default class ExpressionParser extends LValParser {
node.argument = this.parseMaybeUnary(); node.argument = this.parseMaybeUnary();
if (refShorthandDefaultPos && refShorthandDefaultPos.start) { this.checkExpressionErrors(refExpressionErrors, true);
this.unexpected(refShorthandDefaultPos.start);
}
if (update) { if (update) {
this.checkLVal(node.argument, undefined, undefined, "prefix operation"); this.checkLVal(node.argument, undefined, undefined, "prefix operation");
@ -506,8 +517,8 @@ export default class ExpressionParser extends LValParser {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
let expr = this.parseExprSubscripts(refShorthandDefaultPos); let expr = this.parseExprSubscripts(refExpressionErrors);
if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; if (this.checkExpressionErrors(refExpressionErrors, false)) return expr;
while (this.state.type.postfix && !this.canInsertSemicolon()) { while (this.state.type.postfix && !this.canInsertSemicolon()) {
const node = this.startNodeAt(startPos, startLoc); const node = this.startNodeAt(startPos, startLoc);
node.operator = this.state.value; node.operator = this.state.value;
@ -522,11 +533,11 @@ export default class ExpressionParser extends LValParser {
// Parse call, dot, and `[]`-subscript expressions. // Parse call, dot, and `[]`-subscript expressions.
parseExprSubscripts(refShorthandDefaultPos: ?Pos): N.Expression { parseExprSubscripts(refExpressionErrors: ?ExpressionErrors): N.Expression {
const startPos = this.state.start; const startPos = this.state.start;
const startLoc = this.state.startLoc; const startLoc = this.state.startLoc;
const potentialArrowAt = this.state.potentialArrowAt; const potentialArrowAt = this.state.potentialArrowAt;
const expr = this.parseExprAtom(refShorthandDefaultPos); const expr = this.parseExprAtom(refExpressionErrors);
if ( if (
expr.type === "ArrowFunctionExpression" && expr.type === "ArrowFunctionExpression" &&
@ -535,7 +546,7 @@ export default class ExpressionParser extends LValParser {
return expr; return expr;
} }
if (refShorthandDefaultPos && refShorthandDefaultPos.start) { if (this.checkExpressionErrors(refExpressionErrors, false)) {
return expr; return expr;
} }
@ -816,7 +827,7 @@ export default class ExpressionParser extends LValParser {
elts.push( elts.push(
this.parseExprListItem( this.parseExprListItem(
false, false,
possibleAsyncArrow ? { start: 0 } : undefined, possibleAsyncArrow ? new ExpressionErrors() : undefined,
possibleAsyncArrow ? { start: 0 } : undefined, possibleAsyncArrow ? { start: 0 } : undefined,
allowPlaceholder, allowPlaceholder,
), ),
@ -864,7 +875,7 @@ export default class ExpressionParser extends LValParser {
// `new`, or an expression wrapped in punctuation like `()`, `[]`, // `new`, or an expression wrapped in punctuation like `()`, `[]`,
// or `{}`. // or `{}`.
parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression { parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression {
// If a division operator appears in an expression position, the // If a division operator appears in an expression position, the
// tokenizer got confused, and we force it to read a regexp instead. // tokenizer got confused, and we force it to read a regexp instead.
if (this.state.type === tt.slash) this.readRegexp(); if (this.state.type === tt.slash) this.readRegexp();
@ -1038,7 +1049,7 @@ export default class ExpressionParser extends LValParser {
node.elements = this.parseExprList( node.elements = this.parseExprList(
tt.bracketR, tt.bracketR,
true, true,
refShorthandDefaultPos, refExpressionErrors,
node, node,
); );
if (!this.state.maybeInArrowParameters) { if (!this.state.maybeInArrowParameters) {
@ -1056,7 +1067,7 @@ export default class ExpressionParser extends LValParser {
const oldInFSharpPipelineDirectBody = this.state const oldInFSharpPipelineDirectBody = this.state
.inFSharpPipelineDirectBody; .inFSharpPipelineDirectBody;
this.state.inFSharpPipelineDirectBody = false; this.state.inFSharpPipelineDirectBody = false;
const ret = this.parseObj(false, refShorthandDefaultPos); const ret = this.parseObj(false, refExpressionErrors);
this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody;
return ret; return ret;
} }
@ -1263,7 +1274,7 @@ export default class ExpressionParser extends LValParser {
const innerStartPos = this.state.start; const innerStartPos = this.state.start;
const innerStartLoc = this.state.startLoc; const innerStartLoc = this.state.startLoc;
const exprList = []; const exprList = [];
const refShorthandDefaultPos = { start: 0 }; const refExpressionErrors = new ExpressionErrors();
const refNeedsArrowPos = { start: 0 }; const refNeedsArrowPos = { start: 0 };
let first = true; let first = true;
let spreadStart; let spreadStart;
@ -1299,7 +1310,7 @@ export default class ExpressionParser extends LValParser {
exprList.push( exprList.push(
this.parseMaybeAssign( this.parseMaybeAssign(
false, false,
refShorthandDefaultPos, refExpressionErrors,
this.parseParenItem, this.parseParenItem,
refNeedsArrowPos, refNeedsArrowPos,
), ),
@ -1343,9 +1354,7 @@ export default class ExpressionParser extends LValParser {
} }
if (optionalCommaStart) this.unexpected(optionalCommaStart); if (optionalCommaStart) this.unexpected(optionalCommaStart);
if (spreadStart) this.unexpected(spreadStart); if (spreadStart) this.unexpected(spreadStart);
if (refShorthandDefaultPos.start) { this.checkExpressionErrors(refExpressionErrors, true);
this.unexpected(refShorthandDefaultPos.start);
}
if (refNeedsArrowPos.start) this.unexpected(refNeedsArrowPos.start); if (refNeedsArrowPos.start) this.unexpected(refNeedsArrowPos.start);
this.toReferencedListDeep(exprList, /* isParenthesizedExpr */ true); this.toReferencedListDeep(exprList, /* isParenthesizedExpr */ true);
@ -1490,7 +1499,7 @@ export default class ExpressionParser extends LValParser {
parseObj<T: N.ObjectPattern | N.ObjectExpression>( parseObj<T: N.ObjectPattern | N.ObjectExpression>(
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos?: ?Pos, refExpressionErrors?: ?ExpressionErrors,
): T { ): T {
const propHash: any = Object.create(null); const propHash: any = Object.create(null);
let first = true; let first = true;
@ -1511,9 +1520,11 @@ export default class ExpressionParser extends LValParser {
} }
} }
const prop = this.parseObjectMember(isPattern, refShorthandDefaultPos); const prop = this.parseObjectMember(isPattern, refExpressionErrors);
// $FlowIgnore RestElement will never be returned if !isPattern if (!isPattern) {
if (!isPattern) this.checkDuplicatedProto(prop, propHash); // $FlowIgnore RestElement will never be returned if !isPattern
this.checkDuplicatedProto(prop, propHash, refExpressionErrors);
}
// $FlowIgnore // $FlowIgnore
if (prop.shorthand) { if (prop.shorthand) {
@ -1523,10 +1534,6 @@ export default class ExpressionParser extends LValParser {
node.properties.push(prop); node.properties.push(prop);
} }
if (!this.match(tt.eq) && propHash.start !== undefined) {
this.raise(propHash.start, "Redefinition of __proto__ property");
}
return this.finishNode( return this.finishNode(
node, node,
isPattern ? "ObjectPattern" : "ObjectExpression", isPattern ? "ObjectPattern" : "ObjectExpression",
@ -1550,7 +1557,7 @@ export default class ExpressionParser extends LValParser {
parseObjectMember( parseObjectMember(
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos: ?Pos, refExpressionErrors?: ?ExpressionErrors,
): N.ObjectMember | N.SpreadElement | N.RestElement { ): N.ObjectMember | N.SpreadElement | N.RestElement {
let decorators = []; let decorators = [];
if (this.match(tt.at)) { if (this.match(tt.at)) {
@ -1594,7 +1601,7 @@ export default class ExpressionParser extends LValParser {
prop.method = false; prop.method = false;
if (isPattern || refShorthandDefaultPos) { if (isPattern || refExpressionErrors) {
startPos = this.state.start; startPos = this.state.start;
startLoc = this.state.startLoc; startLoc = this.state.startLoc;
} }
@ -1621,7 +1628,7 @@ export default class ExpressionParser extends LValParser {
isGenerator, isGenerator,
isAsync, isAsync,
isPattern, isPattern,
refShorthandDefaultPos, refExpressionErrors,
containsEsc, containsEsc,
); );
@ -1715,14 +1722,14 @@ export default class ExpressionParser extends LValParser {
startPos: ?number, startPos: ?number,
startLoc: ?Position, startLoc: ?Position,
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos: ?Pos, refExpressionErrors: ?ExpressionErrors,
): ?N.ObjectProperty { ): ?N.ObjectProperty {
prop.shorthand = false; prop.shorthand = false;
if (this.eat(tt.colon)) { if (this.eat(tt.colon)) {
prop.value = isPattern prop.value = isPattern
? this.parseMaybeDefault(this.state.start, this.state.startLoc) ? this.parseMaybeDefault(this.state.start, this.state.startLoc)
: this.parseMaybeAssign(false, refShorthandDefaultPos); : this.parseMaybeAssign(false, refExpressionErrors);
return this.finishNode(prop, "ObjectProperty"); return this.finishNode(prop, "ObjectProperty");
} }
@ -1736,9 +1743,9 @@ export default class ExpressionParser extends LValParser {
startLoc, startLoc,
prop.key.__clone(), prop.key.__clone(),
); );
} else if (this.match(tt.eq) && refShorthandDefaultPos) { } else if (this.match(tt.eq) && refExpressionErrors) {
if (!refShorthandDefaultPos.start) { if (refExpressionErrors.shorthandAssign === -1) {
refShorthandDefaultPos.start = this.state.start; refExpressionErrors.shorthandAssign = this.state.start;
} }
prop.value = this.parseMaybeDefault( prop.value = this.parseMaybeDefault(
startPos, startPos,
@ -1761,7 +1768,7 @@ export default class ExpressionParser extends LValParser {
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos: ?Pos, refExpressionErrors?: ?ExpressionErrors,
containsEsc: boolean, containsEsc: boolean,
): void { ): void {
const node = const node =
@ -1777,7 +1784,7 @@ export default class ExpressionParser extends LValParser {
startPos, startPos,
startLoc, startLoc,
isPattern, isPattern,
refShorthandDefaultPos, refExpressionErrors,
); );
if (!node) this.unexpected(); if (!node) this.unexpected();
@ -2019,7 +2026,7 @@ export default class ExpressionParser extends LValParser {
parseExprList( parseExprList(
close: TokenType, close: TokenType,
allowEmpty?: boolean, allowEmpty?: boolean,
refShorthandDefaultPos?: ?Pos, refExpressionErrors?: ?ExpressionErrors,
nodeForExtra?: ?N.Node, nodeForExtra?: ?N.Node,
): $ReadOnlyArray<?N.Expression> { ): $ReadOnlyArray<?N.Expression> {
const elts = []; const elts = [];
@ -2043,14 +2050,14 @@ export default class ExpressionParser extends LValParser {
} }
} }
elts.push(this.parseExprListItem(allowEmpty, refShorthandDefaultPos)); elts.push(this.parseExprListItem(allowEmpty, refExpressionErrors));
} }
return elts; return elts;
} }
parseExprListItem( parseExprListItem(
allowEmpty: ?boolean, allowEmpty: ?boolean,
refShorthandDefaultPos: ?Pos, refExpressionErrors?: ?ExpressionErrors,
refNeedsArrowPos: ?Pos, refNeedsArrowPos: ?Pos,
allowPlaceholder: ?boolean, allowPlaceholder: ?boolean,
): ?N.Expression { ): ?N.Expression {
@ -2061,7 +2068,7 @@ export default class ExpressionParser extends LValParser {
const spreadNodeStartPos = this.state.start; const spreadNodeStartPos = this.state.start;
const spreadNodeStartLoc = this.state.startLoc; const spreadNodeStartLoc = this.state.startLoc;
elt = this.parseParenItem( elt = this.parseParenItem(
this.parseSpread(refShorthandDefaultPos, refNeedsArrowPos), this.parseSpread(refExpressionErrors, refNeedsArrowPos),
spreadNodeStartPos, spreadNodeStartPos,
spreadNodeStartLoc, spreadNodeStartLoc,
); );
@ -2076,7 +2083,7 @@ export default class ExpressionParser extends LValParser {
} else { } else {
elt = this.parseMaybeAssign( elt = this.parseMaybeAssign(
false, false,
refShorthandDefaultPos, refExpressionErrors,
this.parseParenItem, this.parseParenItem,
refNeedsArrowPos, refNeedsArrowPos,
); );

View File

@ -21,6 +21,7 @@ import {
} from "../util/identifier"; } from "../util/identifier";
import { NodeUtils } from "./node"; import { NodeUtils } from "./node";
import { type BindingTypes, BIND_NONE } from "../util/scopeflags"; import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
import { ExpressionErrors } from "./util";
const unwrapParenthesizedExpression = (node: Node) => { const unwrapParenthesizedExpression = (node: Node) => {
return node.type === "ParenthesizedExpression" return node.type === "ParenthesizedExpression"
@ -33,13 +34,13 @@ export default class LValParser extends NodeUtils {
+parseIdentifier: (liberal?: boolean) => Identifier; +parseIdentifier: (liberal?: boolean) => Identifier;
+parseMaybeAssign: ( +parseMaybeAssign: (
noIn?: ?boolean, noIn?: ?boolean,
refShorthandDefaultPos?: ?Pos, refExpressionErrors?: ?ExpressionErrors,
afterLeftParse?: Function, afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos, refNeedsArrowPos?: ?Pos,
) => Expression; ) => Expression;
+parseObj: <T: ObjectPattern | ObjectExpression>( +parseObj: <T: ObjectPattern | ObjectExpression>(
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos?: ?Pos, refExpressionErrors?: ?ExpressionErrors,
) => T; ) => T;
// Forward-declaration: defined in statement.js // Forward-declaration: defined in statement.js
+parseDecorator: () => Decorator; +parseDecorator: () => Decorator;
@ -241,14 +242,14 @@ export default class LValParser extends NodeUtils {
// Parses spread element. // Parses spread element.
parseSpread( parseSpread(
refShorthandDefaultPos: ?Pos, refExpressionErrors: ?ExpressionErrors,
refNeedsArrowPos?: ?Pos, refNeedsArrowPos?: ?Pos,
): SpreadElement { ): SpreadElement {
const node = this.startNode(); const node = this.startNode();
this.next(); this.next();
node.argument = this.parseMaybeAssign( node.argument = this.parseMaybeAssign(
false, false,
refShorthandDefaultPos, refExpressionErrors,
undefined, undefined,
refNeedsArrowPos, refNeedsArrowPos,
); );

View File

@ -27,6 +27,7 @@ import {
CLASS_ELEMENT_STATIC_SETTER, CLASS_ELEMENT_STATIC_SETTER,
type BindingTypes, type BindingTypes,
} from "../util/scopeflags"; } from "../util/scopeflags";
import { ExpressionErrors } from "./util";
const loopLabel = { kind: "loop" }, const loopLabel = { kind: "loop" },
switchLabel = { kind: "switch" }; switchLabel = { kind: "switch" };
@ -533,8 +534,8 @@ export default class StatementParser extends ExpressionParser {
return this.parseFor(node, init); return this.parseFor(node, init);
} }
const refShorthandDefaultPos = { start: 0 }; const refExpressionErrors = new ExpressionErrors();
const init = this.parseExpression(true, refShorthandDefaultPos); const init = this.parseExpression(true, refExpressionErrors);
if (this.match(tt._in) || this.isContextual("of")) { if (this.match(tt._in) || this.isContextual("of")) {
const description = this.isContextual("of") const description = this.isContextual("of")
? "for-of statement" ? "for-of statement"
@ -542,8 +543,8 @@ export default class StatementParser extends ExpressionParser {
this.toAssignable(init, undefined, description); this.toAssignable(init, undefined, description);
this.checkLVal(init, undefined, undefined, description); this.checkLVal(init, undefined, undefined, description);
return this.parseForIn(node, init, awaitAt); return this.parseForIn(node, init, awaitAt);
} else if (refShorthandDefaultPos.start) { } else {
this.unexpected(refShorthandDefaultPos.start); this.checkExpressionErrors(refExpressionErrors, true);
} }
if (awaitAt > -1) { if (awaitAt > -1) {
this.unexpected(awaitAt); this.unexpected(awaitAt);

View File

@ -268,4 +268,35 @@ export default class UtilParser extends Tokenizer {
throw error; throw error;
} }
} }
checkExpressionErrors(
refExpressionErrors: ?ExpressionErrors,
andThrow: boolean,
) {
if (!refExpressionErrors) return false;
const { shorthandAssign, doubleProto } = refExpressionErrors;
if (!andThrow) return shorthandAssign >= 0 || doubleProto >= 0;
if (shorthandAssign >= 0) {
this.unexpected(shorthandAssign);
}
if (doubleProto >= 0) {
this.raise(doubleProto, "Redefinition of __proto__ property");
}
}
}
/**
* The ExpressionErrors is a context struct used to track
* - **shorthandAssign**: track initializer `=` position when parsing ambiguous
* patterns. When we are sure the parsed pattern is a RHS, which means it is
* not a pattern, we will throw on this position on invalid assign syntax,
* otherwise it will be reset to -1
* - **doubleProto**: track the duplicate `__proto__` key position when parsing
* ambiguous object patterns. When we are sure the parsed pattern is a RHS,
* which means it is an object literal, we will throw on this position for
* __proto__ redefinition, otherwise it will be reset to -1
*/
export class ExpressionErrors {
shorthandAssign = -1;
doubleProto = -1;
} }

View File

@ -4,8 +4,9 @@
import { types as tt, TokenType } from "../tokenizer/types"; import { types as tt, TokenType } from "../tokenizer/types";
import type Parser from "../parser"; import type Parser from "../parser";
import type { ExpressionErrors } from "../parser/util";
import * as N from "../types"; import * as N from "../types";
import type { Pos, Position } from "../util/location"; import type { Position } from "../util/location";
import { type BindingTypes, BIND_NONE } from "../util/scopeflags"; import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
function isSimpleProperty(node: N.Node): boolean { function isSimpleProperty(node: N.Node): boolean {
@ -148,7 +149,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
checkDuplicatedProto( checkDuplicatedProto(
prop: N.ObjectMember | N.SpreadElement, prop: N.ObjectMember | N.SpreadElement,
protoRef: { used: boolean, start?: number }, protoRef: { used: boolean },
refExpressionErrors: ?ExpressionErrors,
): void { ): void {
if ( if (
prop.type === "SpreadElement" || prop.type === "SpreadElement" ||
@ -166,8 +168,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if (name === "__proto__" && prop.kind === "init") { if (name === "__proto__" && prop.kind === "init") {
// Store the first redefinition's position // Store the first redefinition's position
if (protoRef.used && !protoRef.start) { if (protoRef.used) {
protoRef.start = key.start; if (refExpressionErrors && refExpressionErrors.doubleProto === -1) {
refExpressionErrors.doubleProto = key.start;
} else {
this.raise(key.start, "Redefinition of __proto__ property");
}
} }
protoRef.used = true; protoRef.used = true;
@ -234,7 +240,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
classBody.body.push(method); classBody.body.push(method);
} }
parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression { parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression {
switch (this.state.type) { switch (this.state.type) {
case tt.num: case tt.num:
case tt.string: case tt.string:
@ -256,7 +262,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.estreeParseLiteral(false); return this.estreeParseLiteral(false);
default: default:
return super.parseExprAtom(refShorthandDefaultPos); return super.parseExprAtom(refExpressionErrors);
} }
} }
@ -340,14 +346,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
startPos: ?number, startPos: ?number,
startLoc: ?Position, startLoc: ?Position,
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos: ?Pos, refExpressionErrors: ?ExpressionErrors,
): ?N.ObjectProperty { ): ?N.ObjectProperty {
const node: N.EstreeProperty = (super.parseObjectProperty( const node: N.EstreeProperty = (super.parseObjectProperty(
prop, prop,
startPos, startPos,
startLoc, startLoc,
isPattern, isPattern,
refShorthandDefaultPos, refExpressionErrors,
): any); ): any);
if (node) { if (node) {

View File

@ -21,6 +21,7 @@ import {
SCOPE_ARROW, SCOPE_ARROW,
SCOPE_OTHER, SCOPE_OTHER,
} from "../util/scopeflags"; } from "../util/scopeflags";
import type { ExpressionErrors } from "../parser/util";
const reservedTypes = new Set([ const reservedTypes = new Set([
"_", "_",
@ -2283,7 +2284,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean, isGenerator: boolean,
isAsync: boolean, isAsync: boolean,
isPattern: boolean, isPattern: boolean,
refShorthandDefaultPos: ?Pos, refExpressionErrors: ?ExpressionErrors,
containsEsc: boolean, containsEsc: boolean,
): void { ): void {
if ((prop: $FlowFixMe).variance) { if ((prop: $FlowFixMe).variance) {
@ -2306,7 +2307,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator, isGenerator,
isAsync, isAsync,
isPattern, isPattern,
refShorthandDefaultPos, refExpressionErrors,
containsEsc, containsEsc,
); );
@ -2559,7 +2560,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// 3. This is neither. Just call the super method // 3. This is neither. Just call the super method
parseMaybeAssign( parseMaybeAssign(
noIn?: ?boolean, noIn?: ?boolean,
refShorthandDefaultPos?: ?Pos, refExpressionErrors?: ?ExpressionErrors,
afterLeftParse?: Function, afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos, refNeedsArrowPos?: ?Pos,
): N.Expression { ): N.Expression {
@ -2577,7 +2578,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
() => () =>
super.parseMaybeAssign( super.parseMaybeAssign(
noIn, noIn,
refShorthandDefaultPos, refExpressionErrors,
afterLeftParse, afterLeftParse,
refNeedsArrowPos, refNeedsArrowPos,
), ),
@ -2611,7 +2612,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
() => () =>
super.parseMaybeAssign( super.parseMaybeAssign(
noIn, noIn,
refShorthandDefaultPos, refExpressionErrors,
afterLeftParse, afterLeftParse,
refNeedsArrowPos, refNeedsArrowPos,
), ),
@ -2659,7 +2660,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return super.parseMaybeAssign( return super.parseMaybeAssign(
noIn, noIn,
refShorthandDefaultPos, refExpressionErrors,
afterLeftParse, afterLeftParse,
refNeedsArrowPos, refNeedsArrowPos,
); );

View File

@ -4,11 +4,12 @@ import * as charCodes from "charcodes";
import XHTMLEntities from "./xhtml"; import XHTMLEntities from "./xhtml";
import type Parser from "../../parser"; import type Parser from "../../parser";
import type { ExpressionErrors } from "../../parser/util";
import { TokenType, types as tt } from "../../tokenizer/types"; import { TokenType, types as tt } from "../../tokenizer/types";
import { TokContext, types as tc } from "../../tokenizer/context"; import { TokContext, types as tc } from "../../tokenizer/context";
import * as N from "../../types"; import * as N from "../../types";
import { isIdentifierChar, isIdentifierStart } from "../../util/identifier"; import { isIdentifierChar, isIdentifierStart } from "../../util/identifier";
import type { Pos, Position } from "../../util/location"; import type { Position } from "../../util/location";
import { isNewLine } from "../../util/whitespace"; import { isNewLine } from "../../util/whitespace";
const HEX_NUMBER = /^[\da-fA-F]+$/; const HEX_NUMBER = /^[\da-fA-F]+$/;
@ -510,7 +511,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// Overrides // Overrides
// ================================== // ==================================
parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression { parseExprAtom(refExpressionErrors: ?ExpressionErrors): N.Expression {
if (this.match(tt.jsxText)) { if (this.match(tt.jsxText)) {
return this.parseLiteral(this.state.value, "JSXText"); return this.parseLiteral(this.state.value, "JSXText");
} else if (this.match(tt.jsxTagStart)) { } else if (this.match(tt.jsxTagStart)) {
@ -524,7 +525,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.finishToken(tt.jsxTagStart); this.finishToken(tt.jsxTagStart);
return this.jsxParseElement(); return this.jsxParseElement();
} else { } else {
return super.parseExprAtom(refShortHandDefaultPos); return super.parseExprAtom(refExpressionErrors);
} }
} }

View File

@ -25,6 +25,7 @@ import {
} from "../../util/scopeflags"; } from "../../util/scopeflags";
import TypeScriptScopeHandler from "./scope"; import TypeScriptScopeHandler from "./scope";
import * as charCodes from "charcodes"; import * as charCodes from "charcodes";
import type { ExpressionErrors } from "../../parser/util";
type TsModifier = type TsModifier =
| "readonly" | "readonly"
@ -2340,11 +2341,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
// Handle type assertions // Handle type assertions
parseMaybeUnary(refShorthandDefaultPos?: ?Pos): N.Expression { parseMaybeUnary(refExpressionErrors?: ?ExpressionErrors): N.Expression {
if (!this.hasPlugin("jsx") && this.isRelational("<")) { if (!this.hasPlugin("jsx") && this.isRelational("<")) {
return this.tsParseTypeAssertion(); return this.tsParseTypeAssertion();
} else { } else {
return super.parseMaybeUnary(refShorthandDefaultPos); return super.parseMaybeUnary(refExpressionErrors);
} }
} }

View File

@ -0,0 +1 @@
([{ __proto__: x, __proto__: y }] = [{}]);

View File

@ -0,0 +1,241 @@
{
"type": "File",
"start": 0,
"end": 42,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 42
}
},
"program": {
"type": "Program",
"start": 0,
"end": 42,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 42
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 42,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 42
}
},
"expression": {
"type": "AssignmentExpression",
"start": 1,
"end": 40,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 40
}
},
"operator": "=",
"left": {
"type": "ArrayPattern",
"start": 1,
"end": 33,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 33
}
},
"elements": [
{
"type": "ObjectPattern",
"start": 2,
"end": 32,
"loc": {
"start": {
"line": 1,
"column": 2
},
"end": {
"line": 1,
"column": 32
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 4,
"end": 16,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 16
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 4,
"end": 13,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 13
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 15,
"end": 16,
"loc": {
"start": {
"line": 1,
"column": 15
},
"end": {
"line": 1,
"column": 16
},
"identifierName": "x"
},
"name": "x"
}
},
{
"type": "ObjectProperty",
"start": 18,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 30
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 18,
"end": 27,
"loc": {
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 27
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 29,
"end": 30,
"loc": {
"start": {
"line": 1,
"column": 29
},
"end": {
"line": 1,
"column": 30
},
"identifierName": "y"
},
"name": "y"
}
}
]
}
]
},
"right": {
"type": "ArrayExpression",
"start": 36,
"end": 40,
"loc": {
"start": {
"line": 1,
"column": 36
},
"end": {
"line": 1,
"column": 40
}
},
"elements": [
{
"type": "ObjectExpression",
"start": 37,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 37
},
"end": {
"line": 1,
"column": 39
}
},
"properties": []
}
]
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
}
}
],
"directives": []
}
}

View File

@ -0,0 +1 @@
({ __proto__: x, __proto__: y }) => {};

View File

@ -0,0 +1,208 @@
{
"type": "File",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"program": {
"type": "Program",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"expression": {
"type": "ArrowFunctionExpression",
"start": 0,
"end": 38,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 38
}
},
"id": null,
"generator": false,
"async": false,
"params": [
{
"type": "ObjectPattern",
"start": 1,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 31
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 3,
"end": 15,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 15
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 3,
"end": 12,
"loc": {
"start": {
"line": 1,
"column": 3
},
"end": {
"line": 1,
"column": 12
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 14,
"end": 15,
"loc": {
"start": {
"line": 1,
"column": 14
},
"end": {
"line": 1,
"column": 15
},
"identifierName": "x"
},
"name": "x"
}
},
{
"type": "ObjectProperty",
"start": 17,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 17
},
"end": {
"line": 1,
"column": 29
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 17,
"end": 26,
"loc": {
"start": {
"line": 1,
"column": 17
},
"end": {
"line": 1,
"column": 26
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 28,
"end": 29,
"loc": {
"start": {
"line": 1,
"column": 28
},
"end": {
"line": 1,
"column": 29
},
"identifierName": "y"
},
"name": "y"
}
}
]
}
],
"body": {
"type": "BlockStatement",
"start": 36,
"end": 38,
"loc": {
"start": {
"line": 1,
"column": 36
},
"end": {
"line": 1,
"column": 38
}
},
"body": [],
"directives": []
}
}
}
],
"directives": []
}
}

View File

@ -0,0 +1 @@
new {__proto__: Number, __proto__: Number}.__proto__;

View File

@ -0,0 +1,223 @@
{
"type": "File",
"start": 0,
"end": 53,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 53
}
},
"errors": [
"SyntaxError: Redefinition of __proto__ property (1:24)"
],
"program": {
"type": "Program",
"start": 0,
"end": 53,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 53
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 53,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 53
}
},
"expression": {
"type": "NewExpression",
"start": 0,
"end": 52,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 52
}
},
"callee": {
"type": "MemberExpression",
"start": 4,
"end": 52,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 52
}
},
"object": {
"type": "ObjectExpression",
"start": 4,
"end": 42,
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 42
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 5,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 5
},
"end": {
"line": 1,
"column": 22
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 5,
"end": 14,
"loc": {
"start": {
"line": 1,
"column": 5
},
"end": {
"line": 1,
"column": 14
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 16,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 16
},
"end": {
"line": 1,
"column": 22
},
"identifierName": "Number"
},
"name": "Number"
}
},
{
"type": "ObjectProperty",
"start": 24,
"end": 41,
"loc": {
"start": {
"line": 1,
"column": 24
},
"end": {
"line": 1,
"column": 41
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 24,
"end": 33,
"loc": {
"start": {
"line": 1,
"column": 24
},
"end": {
"line": 1,
"column": 33
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 35,
"end": 41,
"loc": {
"start": {
"line": 1,
"column": 35
},
"end": {
"line": 1,
"column": 41
},
"identifierName": "Number"
},
"name": "Number"
}
}
]
},
"property": {
"type": "Identifier",
"start": 43,
"end": 52,
"loc": {
"start": {
"line": 1,
"column": 43
},
"end": {
"line": 1,
"column": 52
},
"identifierName": "__proto__"
},
"name": "__proto__"
},
"computed": false
},
"arguments": []
}
}
],
"directives": []
}
}