forbid `AST_Await` in computed function arguments (#4352)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 8 Dec 2020 04:59:08 +0000 (04:59 +0000)
committerGitHub <noreply@github.com>
Tue, 8 Dec 2020 04:59:08 +0000 (12:59 +0800)
fixes #4351

lib/parse.js
test/mocha/async.js

index 21e6055..c65b2f4 100644 (file)
@@ -653,14 +653,15 @@ function parse($TEXT, options) {
         input         : typeof $TEXT == "string"
                         ? tokenizer($TEXT, options.filename, options.html5_comments, options.shebang)
                         : $TEXT,
-        token         : null,
-        prev          : null,
-        peeked        : null,
         in_async      : false,
-        in_function   : 0,
         in_directives : true,
+        in_funarg     : -1,
+        in_function   : 0,
         in_loop       : 0,
-        labels        : []
+        labels        : [],
+        peeked        : null,
+        prev          : null,
+        token         : null,
     };
 
     S.token = next();
@@ -1053,9 +1054,12 @@ function parse($TEXT, options) {
         if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration))
             unexpected(prev());
         expect("(");
+        var was_funarg = S.in_funarg;
+        S.in_funarg = S.in_function;
         var argnames = expr_list(")", !options.strict, false, function() {
             return maybe_destructured(AST_SymbolFunarg);
         });
+        S.in_funarg = was_funarg;
         var loop = S.in_loop;
         var labels = S.labels;
         ++S.in_function;
@@ -1638,6 +1642,7 @@ function parse($TEXT, options) {
     function maybe_await() {
         var start = S.token;
         if (!(S.in_async && is("name", "await"))) return maybe_unary();
+        if (S.in_funarg === S.in_function) croak("Invalid use of await in function argument");
         S.input.context().regex_allowed = true;
         next();
         return new AST_Await({
index dc1aa2f..d4e9660 100644 (file)
@@ -12,7 +12,12 @@ describe("async", function() {
             "function() { function await() {} }",
             "function() { try {} catch (await) {} }",
         ].forEach(function(code) {
-            UglifyJS.parse("(" + code + ")();");
+            var ast = UglifyJS.parse("(" + code + ")();");
+            assert.strictEqual(ast.TYPE, "Toplevel");
+            assert.strictEqual(ast.body.length, 1);
+            assert.strictEqual(ast.body[0].TYPE, "SimpleStatement");
+            assert.strictEqual(ast.body[0].body.TYPE, "Call");
+            assert.strictEqual(ast.body[0].body.expression.TYPE, "Function");
             assert.throws(function() {
                 UglifyJS.parse("(async " + code + ")();");
             }, function(e) {
@@ -20,4 +25,41 @@ describe("async", function() {
             }, code);
         });
     });
+    it("Should reject `await` expression outside of async functions", function() {
+        [
+            "await 42;",
+            "function f() { await 42; }",
+            "async function f() { function g() { await 42; } }",
+        ].forEach(function(code) {
+            assert.throws(function() {
+                UglifyJS.parse(code);
+            }, function(e) {
+                return e instanceof UglifyJS.JS_Parse_Error;
+            }, code);
+        });
+    });
+    it("Should reject `await` expression directly on computed key of function argument", function() {
+        [
+            "function f({ [await 42]: a }) {}",
+            "async function f({ [await 42]: a }) {}",
+        ].forEach(function(code) {
+            assert.throws(function() {
+                UglifyJS.parse(code);
+            }, function(e) {
+                return e instanceof UglifyJS.JS_Parse_Error;
+            }, code);
+        });
+    });
+    it("Should accept `await` expression nested within computed key of function argument", function() {
+        [
+            "function f({ [async function() { await 42; }()]: a }) {}",
+            "async function f({ [async function() { await 42; }()]: a }) {}",
+        ].forEach(function(code) {
+            var ast = UglifyJS.parse(code);
+            assert.strictEqual(ast.TYPE, "Toplevel");
+            assert.strictEqual(ast.body.length, 1);
+            assert.strictEqual(ast.body[0].argnames.length, 1);
+            assert.strictEqual(ast.body[0].argnames[0].TYPE, "DestructuredObject");
+        });
+    });
 });