Skip to content

Commit 064bec7

Browse files
authored
Reject using declarations directly in for loop or switch scopes.
FIX: Properly reject `using` declarations that appear directly in `switch` or `for` head scopes.
1 parent e37a9c3 commit 064bec7

File tree

5 files changed

+30
-8
lines changed

5 files changed

+30
-8
lines changed

acorn/src/scopeflags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const
1010
SCOPE_DIRECT_SUPER = 128,
1111
SCOPE_CLASS_STATIC_BLOCK = 256,
1212
SCOPE_CLASS_FIELD_INIT = 512,
13+
SCOPE_SWITCH = 1024,
1314
SCOPE_VAR = SCOPE_TOP | SCOPE_FUNCTION | SCOPE_CLASS_STATIC_BLOCK
1415

1516
export function functionFlags(async, generator) {

acorn/src/state.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {getOptions} from "./options.js"
55
import {wordsRegexp} from "./util.js"
66
import {
77
SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_SUPER, SCOPE_DIRECT_SUPER,
8-
SCOPE_ARROW, SCOPE_CLASS_STATIC_BLOCK, SCOPE_CLASS_FIELD_INIT
8+
SCOPE_ARROW, SCOPE_CLASS_STATIC_BLOCK, SCOPE_CLASS_FIELD_INIT, SCOPE_SWITCH
99
} from "./scopeflags.js"
1010

1111
export class Parser {
@@ -133,6 +133,13 @@ export class Parser {
133133
return false
134134
}
135135

136+
get allowUsing() {
137+
const {flags} = this.currentScope()
138+
if (flags & SCOPE_SWITCH) return false
139+
if (!this.inModule && flags & SCOPE_TOP) return false
140+
return true
141+
}
142+
136143
get inClassStaticBlock() {
137144
return (this.currentVarScope().flags & SCOPE_CLASS_STATIC_BLOCK) > 0
138145
}

acorn/src/statement.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {lineBreak, skipWhiteSpace} from "./whitespace.js"
44
import {isIdentifierStart, isIdentifierChar, keywordRelationalOperator} from "./identifier.js"
55
import {hasOwn, loneSurrogate} from "./util.js"
66
import {DestructuringErrors} from "./parseutil.js"
7-
import {functionFlags, SCOPE_SIMPLE_CATCH, BIND_SIMPLE_CATCH, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, SCOPE_CLASS_STATIC_BLOCK, SCOPE_SUPER, SCOPE_CLASS_FIELD_INIT} from "./scopeflags.js"
7+
import {functionFlags, SCOPE_SIMPLE_CATCH, BIND_SIMPLE_CATCH, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, SCOPE_CLASS_STATIC_BLOCK, SCOPE_SUPER, SCOPE_CLASS_FIELD_INIT, SCOPE_SWITCH} from "./scopeflags.js"
88

99
const pp = Parser.prototype
1010

@@ -194,8 +194,8 @@ pp.parseStatement = function(context, topLevel, exports) {
194194

195195
let usingKind = this.isAwaitUsing(false) ? "await using" : this.isUsing(false) ? "using" : null
196196
if (usingKind) {
197-
if (topLevel && this.options.sourceType === "script") {
198-
this.raise(this.start, "Using declaration cannot appear in the top level when source type is `script`")
197+
if (!this.allowUsing) {
198+
this.raise(this.start, "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement")
199199
}
200200
if (usingKind === "await using") {
201201
if (!this.canAwait) {
@@ -292,7 +292,12 @@ pp.parseForStatement = function(node) {
292292
if (usingKind) {
293293
let init = this.startNode()
294294
this.next()
295-
if (usingKind === "await using") this.next()
295+
if (usingKind === "await using") {
296+
if (!this.canAwait) {
297+
this.raise(this.start, "Await using cannot appear outside of async function")
298+
}
299+
this.next()
300+
}
296301
this.parseVar(init, true, usingKind)
297302
this.finishNode(init, "VariableDeclaration")
298303
return this.parseForAfterInit(node, init, awaitAt)
@@ -370,7 +375,7 @@ pp.parseSwitchStatement = function(node) {
370375
node.cases = []
371376
this.expect(tt.braceL)
372377
this.labels.push(switchLabel)
373-
this.enterScope(0)
378+
this.enterScope(SCOPE_SWITCH)
374379

375380
// Statements under must be grouped (by label) in SwitchCase
376381
// nodes. `cur` is used to keep the node that we are currently

bin/test262.whitelist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
staging/explicit-resource-management/await-using-in-switch-case-block.js (default)
2+
staging/explicit-resource-management/await-using-in-switch-case-block.js (strict mode)
3+
staging/explicit-resource-management/call-dispose-methods.js (default)
4+
staging/explicit-resource-management/call-dispose-methods.js (strict mode)

test/tests-using.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,14 +1102,14 @@ testFail("let await using x = resource;", "Cannot use keyword 'await' outside an
11021102
// let using is not allowed
11031103
testFail("let using x = resource;", "Unexpected token (1:10)", {ecmaVersion: 17, sourceType: "module"});
11041104
// top level using is not allowed
1105-
testFail("using x = resource;", "Using declaration cannot appear in the top level when source type is `script` (1:0)", {ecmaVersion: 17, sourceType: "script"});
1105+
testFail("using x = resource;", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:0)", {ecmaVersion: 17, sourceType: "script"});
11061106

11071107
// BoundNames contains "let"
11081108
testFail("async function test() { await using let = resource; }", "The keyword 'let' is reserved (1:36)", {ecmaVersion: 17, sourceType: "module"});
11091109
// BoundNames contains duplicate entries
11101110
testFail("async function test() { await using x = resource1, x = resource2; }", "Identifier 'x' has already been declared (1:51)", {ecmaVersion: 17, sourceType: "module"});
11111111
// top level await using is not allowed
1112-
testFail("await using x = resource;", "Using declaration cannot appear in the top level when source type is `script` (1:0)", {ecmaVersion: 17, sourceType: "script"});
1112+
testFail("await using x = resource;", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:0)", {ecmaVersion: 17, sourceType: "script"});
11131113

11141114
// Basic missing initializer
11151115
testFail("{ using x; }", "Missing initializer in using declaration (1:9)", {ecmaVersion: 17, sourceType: "module"});
@@ -1178,6 +1178,11 @@ testFail("{ using \\u0030x = resource; }", "Invalid Unicode escape (1:8)", {ecma
11781178

11791179
// await using in script mode
11801180
testFail("{await using a = x}", "Await using cannot appear outside of async function (1:1)", {ecmaVersion: 17, sourceType: "script"});
1181+
testFail("for (await using a of x) {}", "Await using cannot appear outside of async function (1:11)", {ecmaVersion: 17, sourceType: "script"});
1182+
1183+
// Using in a bare case statement (should not be allowed)
1184+
testFail("switch (x) { case 1: using y = resource; }", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:21)", {ecmaVersion: 17, sourceType: "module"});
1185+
testFail("switch (x) { case 1: break; default: using y = resource; }", "Using declaration cannot appear in the top level when source type is `script` or in the bare case statement (1:37)", {ecmaVersion: 17, sourceType: "module"});
11811186

11821187
// =============================================================================
11831188
// EDGE CASES - Unusual but valid scenarios and boundary conditions

0 commit comments

Comments
 (0)