Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow non octal decimal escape before use strict #12366

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
@@ -1,5 +1,5 @@
FLOW_COMMIT = a1f9a4c709dcebb27a5084acf47755fbae699c25
TEST262_COMMIT = d9740c172652d36194ceae3ed3d0484e9968ebc3
TEST262_COMMIT = 36d2d2d348d83e9d6554af59a672fbcd9413914b
TYPESCRIPT_COMMIT = da8633212023517630de5f3620a23736b63234b1

FORCE_PUBLISH = -f @babel/runtime -f @babel/runtime-corejs2 -f @babel/runtime-corejs3 -f @babel/standalone
Expand Down
44 changes: 19 additions & 25 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -810,6 +810,9 @@ export default class StatementParser extends ExpressionParser {
afterBlockParse?: (hasStrictModeDirective: boolean) => void,
): N.BlockStatement {
const node = this.startNode();
if (allowDirectives) {
this.state.strictErrors.clear();
}
this.expect(tt.braceL);
if (createNewLexicalScope) {
this.scope.enter(SCOPE_OTHER);
Expand Down Expand Up @@ -863,44 +866,35 @@ export default class StatementParser extends ExpressionParser {
end: TokenType,
afterBlockParse?: (hasStrictModeDirective: boolean) => void,
): void {
const octalPositions = [];
const oldStrict = this.state.strict;
let hasStrictModeDirective = false;
let parsedNonDirective = false;

while (!this.match(end)) {
// Track octal literals that occur before a "use strict" directive.
if (!parsedNonDirective && this.state.octalPositions.length) {
octalPositions.push(...this.state.octalPositions);
}

const stmt = this.parseStatement(null, topLevel);

if (directives && !parsedNonDirective && this.isValidDirective(stmt)) {
const directive = this.stmtToDirective(stmt);
directives.push(directive);
if (directives && !parsedNonDirective) {
if (this.isValidDirective(stmt)) {
const directive = this.stmtToDirective(stmt);
directives.push(directive);

if (!hasStrictModeDirective && directive.value.value === "use strict") {
hasStrictModeDirective = true;
this.setStrict(true);
}
if (
!hasStrictModeDirective &&
directive.value.value === "use strict"
) {
hasStrictModeDirective = true;
this.setStrict(true);
}

continue;
continue;
}
parsedNonDirective = true;
// clear strict errors since the strict mode will not change within the block
this.state.strictErrors.clear();
}

parsedNonDirective = true;
body.push(stmt);
}

// Throw an error for any octal literals found before a
// "use strict" directive. Strict mode will be set at parse
// time for any literals that occur after the directive.
if (this.state.strict && octalPositions.length) {
for (const pos of octalPositions) {
this.raise(pos, Errors.StrictOctalLiteral);
}
}

if (afterBlockParse) {
afterBlockParse.call(this, hasStrictModeDirective);
}
Expand Down
47 changes: 26 additions & 21 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -210,14 +210,17 @@ export default class Tokenizer extends ParserErrors {

setStrict(strict: boolean): void {
this.state.strict = strict;
if (!this.match(tt.num) && !this.match(tt.string)) return;
this.state.pos = this.state.start;
while (this.state.pos < this.state.lineStart) {
this.state.lineStart =
this.input.lastIndexOf("\n", this.state.lineStart - 2) + 1;
--this.state.curLine;
if (strict) {
// Throw an error for any string decimal escape found before/immediately
// after a "use strict" directive. Strict mode will be set at parse
// time for any literals that occur after the next node of the strict
// directive.
this.state.strictErrors.forEach((message, pos) =>
/* eslint-disable @babel/development-internal/dry-error-messages */
this.raise(pos, message),
);
this.state.strictErrors.clear();
}
this.nextToken();
}

curContext(): TokContext {
Expand All @@ -230,8 +233,6 @@ export default class Tokenizer extends ParserErrors {
nextToken(): void {
const curContext = this.curContext();
if (!curContext?.preserveSpace) this.skipSpace();

this.state.octalPositions = [];
this.state.start = this.state.pos;
this.state.startLoc = this.state.curPosition();
if (this.state.pos >= this.length) {
Expand Down Expand Up @@ -1121,9 +1122,8 @@ export default class Tokenizer extends ParserErrors {

if (hasLeadingZero) {
const integer = this.input.slice(start, this.state.pos);
if (this.state.strict) {
this.raise(start, Errors.StrictOctalLiteral);
} else {
this.recordStrictModeErrors(start, Errors.StrictOctalLiteral);
if (!this.state.strict) {
// disallow numeric separators in non octal decimals and legacy octal likes
const underscorePos = integer.indexOf("_");
if (underscorePos > 0) {
Expand Down Expand Up @@ -1321,8 +1321,15 @@ export default class Tokenizer extends ParserErrors {
}
}

// Used to read escaped characters
recordStrictModeErrors(pos: number, message: string) {
if (this.state.strict && !this.state.strictErrors.has(pos)) {
this.raise(pos, message);
} else {
this.state.strictErrors.set(pos, message);
}
}

// Used to read escaped characters
readEscapedChar(inTemplate: boolean): string | null {
const throwOnInvalid = !inTemplate;
const ch = this.input.charCodeAt(++this.state.pos);
Expand Down Expand Up @@ -1364,8 +1371,11 @@ export default class Tokenizer extends ParserErrors {
case charCodes.digit9:
if (inTemplate) {
return null;
} else if (this.state.strict) {
this.raise(this.state.pos - 1, Errors.StrictNumericEscape);
} else {
this.recordStrictModeErrors(
this.state.pos - 1,
Errors.StrictNumericEscape,
);
}
// fall through
default:
Expand Down Expand Up @@ -1393,13 +1403,8 @@ export default class Tokenizer extends ParserErrors {
) {
if (inTemplate) {
return null;
} else if (this.state.strict) {
this.raise(codePos, Errors.StrictNumericEscape);
} else {
// This property is used to throw an error for
// an octal literal in a directive that occurs prior
// to a "use strict" directive.
this.state.octalPositions.push(codePos);
this.recordStrictModeErrors(codePos, Errors.StrictNumericEscape);
}
}

Expand Down
13 changes: 9 additions & 4 deletions packages/babel-parser/src/tokenizer/state.js
Expand Up @@ -137,10 +137,15 @@ export default class State {
// escape sequences must not be interpreted as keywords.
containsEsc: boolean = false;

// This property is used to throw an error for
// an octal literal in a directive that occurs prior
// to a "use strict" directive.
octalPositions: number[] = [];
// This property is used to track the following errors
// - StrictNumericEscape
// - StrictOctalLiteral
//
// in a literal that occurs prior to/immediately after a "use strict" directive.

// todo(JLHwung): set strictErrors to null and avoid recording string errors
// after a non-directive is parsed
Comment on lines +146 to +147
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to do this here or in a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer a separate PR since alternatively, todo is pronounced as never-do. 😛
Legacy octal or numeric escapes are discouraged, I feel like it is okay to leave it as-is.

strictErrors: Map<number, string> = new Map();

// Names of exports store. `default` is stored as a name for both
// `export default foo;` and `export { foo as default };`.
Expand Down
Expand Up @@ -20,13 +20,3 @@ function d() {
"\5";
}

function c() {
"use strict";
05;
}

function d() {
"use strict";
04;
05;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.