Skip to content

Commit

Permalink
Duplicate __proto__ key should be allowed in object patterns (#10987)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JLHwung committed Jan 15, 2020
1 parent a0a9c64 commit 9df70b4
Show file tree
Hide file tree
Showing 16 changed files with 820 additions and 96 deletions.
145 changes: 76 additions & 69 deletions packages/babel-parser/src/parser/expression.js

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions packages/babel-parser/src/parser/lval.js
Expand Up @@ -21,6 +21,7 @@ import {
} from "../util/identifier";
import { NodeUtils } from "./node";
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
import { ExpressionErrors } from "./util";

const unwrapParenthesizedExpression = (node: Node) => {
return node.type === "ParenthesizedExpression"
Expand All @@ -33,13 +34,13 @@ export default class LValParser extends NodeUtils {
+parseIdentifier: (liberal?: boolean) => Identifier;
+parseMaybeAssign: (
noIn?: ?boolean,
refShorthandDefaultPos?: ?Pos,
refExpressionErrors?: ?ExpressionErrors,
afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos,
) => Expression;
+parseObj: <T: ObjectPattern | ObjectExpression>(
isPattern: boolean,
refShorthandDefaultPos?: ?Pos,
refExpressionErrors?: ?ExpressionErrors,
) => T;
// Forward-declaration: defined in statement.js
+parseDecorator: () => Decorator;
Expand Down Expand Up @@ -241,14 +242,14 @@ export default class LValParser extends NodeUtils {
// Parses spread element.

parseSpread(
refShorthandDefaultPos: ?Pos,
refExpressionErrors: ?ExpressionErrors,
refNeedsArrowPos?: ?Pos,
): SpreadElement {
const node = this.startNode();
this.next();
node.argument = this.parseMaybeAssign(
false,
refShorthandDefaultPos,
refExpressionErrors,
undefined,
refNeedsArrowPos,
);
Expand Down
9 changes: 5 additions & 4 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -27,6 +27,7 @@ import {
CLASS_ELEMENT_STATIC_SETTER,
type BindingTypes,
} from "../util/scopeflags";
import { ExpressionErrors } from "./util";

const loopLabel = { kind: "loop" },
switchLabel = { kind: "switch" };
Expand Down Expand Up @@ -533,17 +534,17 @@ export default class StatementParser extends ExpressionParser {
return this.parseFor(node, init);
}

const refShorthandDefaultPos = { start: 0 };
const init = this.parseExpression(true, refShorthandDefaultPos);
const refExpressionErrors = new ExpressionErrors();
const init = this.parseExpression(true, refExpressionErrors);
if (this.match(tt._in) || this.isContextual("of")) {
const description = this.isContextual("of")
? "for-of statement"
: "for-in statement";
this.toAssignable(init, undefined, description);
this.checkLVal(init, undefined, undefined, description);
return this.parseForIn(node, init, awaitAt);
} else if (refShorthandDefaultPos.start) {
this.unexpected(refShorthandDefaultPos.start);
} else {
this.checkExpressionErrors(refExpressionErrors, true);
}
if (awaitAt > -1) {
this.unexpected(awaitAt);
Expand Down
31 changes: 31 additions & 0 deletions packages/babel-parser/src/parser/util.js
Expand Up @@ -268,4 +268,35 @@ export default class UtilParser extends Tokenizer {
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;
}
22 changes: 14 additions & 8 deletions packages/babel-parser/src/plugins/estree.js
Expand Up @@ -4,8 +4,9 @@

import { types as tt, TokenType } from "../tokenizer/types";
import type Parser from "../parser";
import type { ExpressionErrors } from "../parser/util";
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";

function isSimpleProperty(node: N.Node): boolean {
Expand Down Expand Up @@ -148,7 +149,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>

checkDuplicatedProto(
prop: N.ObjectMember | N.SpreadElement,
protoRef: { used: boolean, start?: number },
protoRef: { used: boolean },
refExpressionErrors: ?ExpressionErrors,
): void {
if (
prop.type === "SpreadElement" ||
Expand All @@ -166,8 +168,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>

if (name === "__proto__" && prop.kind === "init") {
// Store the first redefinition's position
if (protoRef.used && !protoRef.start) {
protoRef.start = key.start;
if (protoRef.used) {
if (refExpressionErrors && refExpressionErrors.doubleProto === -1) {
refExpressionErrors.doubleProto = key.start;
} else {
this.raise(key.start, "Redefinition of __proto__ property");
}
}

protoRef.used = true;
Expand Down Expand Up @@ -234,7 +240,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
classBody.body.push(method);
}

parseExprAtom(refShorthandDefaultPos?: ?Pos): N.Expression {
parseExprAtom(refExpressionErrors?: ?ExpressionErrors): N.Expression {
switch (this.state.type) {
case tt.num:
case tt.string:
Expand All @@ -256,7 +262,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.estreeParseLiteral(false);

default:
return super.parseExprAtom(refShorthandDefaultPos);
return super.parseExprAtom(refExpressionErrors);
}
}

Expand Down Expand Up @@ -340,14 +346,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
startPos: ?number,
startLoc: ?Position,
isPattern: boolean,
refShorthandDefaultPos: ?Pos,
refExpressionErrors: ?ExpressionErrors,
): ?N.ObjectProperty {
const node: N.EstreeProperty = (super.parseObjectProperty(
prop,
startPos,
startLoc,
isPattern,
refShorthandDefaultPos,
refExpressionErrors,
): any);

if (node) {
Expand Down
13 changes: 7 additions & 6 deletions packages/babel-parser/src/plugins/flow.js
Expand Up @@ -21,6 +21,7 @@ import {
SCOPE_ARROW,
SCOPE_OTHER,
} from "../util/scopeflags";
import type { ExpressionErrors } from "../parser/util";

const reservedTypes = new Set([
"_",
Expand Down Expand Up @@ -2283,7 +2284,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator: boolean,
isAsync: boolean,
isPattern: boolean,
refShorthandDefaultPos: ?Pos,
refExpressionErrors: ?ExpressionErrors,
containsEsc: boolean,
): void {
if ((prop: $FlowFixMe).variance) {
Expand All @@ -2306,7 +2307,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
isGenerator,
isAsync,
isPattern,
refShorthandDefaultPos,
refExpressionErrors,
containsEsc,
);

Expand Down Expand Up @@ -2559,7 +2560,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// 3. This is neither. Just call the super method
parseMaybeAssign(
noIn?: ?boolean,
refShorthandDefaultPos?: ?Pos,
refExpressionErrors?: ?ExpressionErrors,
afterLeftParse?: Function,
refNeedsArrowPos?: ?Pos,
): N.Expression {
Expand All @@ -2577,7 +2578,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
() =>
super.parseMaybeAssign(
noIn,
refShorthandDefaultPos,
refExpressionErrors,
afterLeftParse,
refNeedsArrowPos,
),
Expand Down Expand Up @@ -2611,7 +2612,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
() =>
super.parseMaybeAssign(
noIn,
refShorthandDefaultPos,
refExpressionErrors,
afterLeftParse,
refNeedsArrowPos,
),
Expand Down Expand Up @@ -2659,7 +2660,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>

return super.parseMaybeAssign(
noIn,
refShorthandDefaultPos,
refExpressionErrors,
afterLeftParse,
refNeedsArrowPos,
);
Expand Down
7 changes: 4 additions & 3 deletions packages/babel-parser/src/plugins/jsx/index.js
Expand Up @@ -4,11 +4,12 @@ import * as charCodes from "charcodes";

import XHTMLEntities from "./xhtml";
import type Parser from "../../parser";
import type { ExpressionErrors } from "../../parser/util";
import { TokenType, types as tt } from "../../tokenizer/types";
import { TokContext, types as tc } from "../../tokenizer/context";
import * as N from "../../types";
import { isIdentifierChar, isIdentifierStart } from "../../util/identifier";
import type { Pos, Position } from "../../util/location";
import type { Position } from "../../util/location";
import { isNewLine } from "../../util/whitespace";

const HEX_NUMBER = /^[\da-fA-F]+$/;
Expand Down Expand Up @@ -510,7 +511,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// Overrides
// ==================================

parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression {
parseExprAtom(refExpressionErrors: ?ExpressionErrors): N.Expression {
if (this.match(tt.jsxText)) {
return this.parseLiteral(this.state.value, "JSXText");
} else if (this.match(tt.jsxTagStart)) {
Expand All @@ -524,7 +525,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.finishToken(tt.jsxTagStart);
return this.jsxParseElement();
} else {
return super.parseExprAtom(refShortHandDefaultPos);
return super.parseExprAtom(refExpressionErrors);
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -25,6 +25,7 @@ import {
} from "../../util/scopeflags";
import TypeScriptScopeHandler from "./scope";
import * as charCodes from "charcodes";
import type { ExpressionErrors } from "../../parser/util";

type TsModifier =
| "readonly"
Expand Down Expand Up @@ -2340,11 +2341,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

// Handle type assertions
parseMaybeUnary(refShorthandDefaultPos?: ?Pos): N.Expression {
parseMaybeUnary(refExpressionErrors?: ?ExpressionErrors): N.Expression {
if (!this.hasPlugin("jsx") && this.isRelational("<")) {
return this.tsParseTypeAssertion();
} else {
return super.parseMaybeUnary(refShorthandDefaultPos);
return super.parseMaybeUnary(refExpressionErrors);
}
}

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

0 comments on commit 9df70b4

Please sign in to comment.