Repository: mishoo/UglifyJS (JavaScript)

Issue:
The `value must be AST_LambdaExpression` failure while compressing valid code
**Uglify version 3.17.0**

I have reproduced the error on the master branch too.

**JavaScript input**

```javascript
function foo() {
    const bar = function (a) {
        return () => { return a; };
    };

    class C {
        func(a) { return bar(a); }
    }
}
```

**The `uglifyjs` CLI command executed.**

```
uglifyjs --compress --validate test.js
```

**JavaScript output or error produced.**

```
if (!(this.value instanceof AST_LambdaExpression)) throw new Error("value must be AST_LambdaExpression");
                                                           ^
Error: value must be AST_LambdaExpression
    at AST_ClassMethod._validate (eval at <anonymous> (test\node.js:3:1), <anonymous>:1293:66)
    at AST_ClassMethod.validate (eval at <anonymous> (test\node.js:3:1), <anonymous>:415:38)
    at ctor.transform (eval at <anonymous> (test\node.js:3:1), <anonymous>:509:22)
    at eval (eval at <anonymous> (test\node.js:3:1), <anonymous>:2702:25)
    at List (eval at <anonymous> (test\node.js:3:1), <anonymous>:103:23)
    at do_list (eval at <anonymous> (test\node.js:3:1), <anonymous>:2701:16)
    at eval (eval at <anonymous> (test\node.js:3:1), <anonymous>:2799:27)
    at Compressor.eval [as before] (eval at <anonymous> (test\node.js:3:1), <anonymous>:6564:5)
    at AST_DefClass.eval (eval at <anonymous> (test\node.js:3:1), <anonymous>:2884:31)
    at ctor.transform (eval at <anonymous> (test\node.js:3:1), <anonymous>:507:34)
```

**Test case to reproduce the problem**

```javascript
case_1: {
    options = {
        inline: true,
        reduce_vars: true,
        unused: true,
    }
    input: {
        function foo() {
            const bar = function (a) {
                return () => { return a; };
            };

            class C {
                func(a) { return bar(a); }
            }
        }
    }
    expect: {
        function foo(){}
    }
}
```


Discussion:
Thanks for the detailed report &minus; investigating.

Once small detail &minus; did you specify `--validate` on the command line? Because without it I cannot reproduce the exception thrown:
```console
$ uglifyjs test.js --compress
function foo(){}
```
```console
$ uglifyjs test.js --compress --validate
ERROR: value must be AST_LambdaExpression
    at _validate (eval at <anonymous>...
```
> Once small detail − did you specify `--validate` on the command line? Because without it I cannot reproduce the exception thrown:

By bad. Yes the `--validate` is required for the reduced test case too. In fact, the original problem was found on `pdf.js` from the [pdf.js 2.16.105](https://github.com/mozilla/pdf.js/releases/tag/v2.16.105). On that file the Uglify is failed with just `--compressed` option, but with the same error and stack trace.
That is very odd then, since that stack trace can only be triggered by `--validate`.

Not the most pressing concern, but just curious if AST validation is somehow enabled when it isn't explicitly specified, since that would have performance implications.
> That is very odd then, since that stack trace can only be triggered by `--validate`.
> 
> Not the most pressing concern, but just curious if AST validation is somehow enabled when it isn't explicitly specified, since that would have performance implications.

I forget to add the `exports.C = C;` clause. The following test case fails with just `--compress`. The test case without `exports.C = C;` fails with `--compress --validate`.

```javascript
function foo() {
    const bar = function (a) {
        return () => { return a; };
    };

    class C {
        func(a) { return bar(a); }
    }

    exports.C = C;
}
```

In this case stack trace is:
```
ERROR: self.name.print is not a function
    at print_lambda (eval at <anonymous> (tools\node.js:18:1), <anonymous>:21626:23)
    at print_method (eval at <anonymous> (tools\node.js:18:1), <anonymous>:21701:9)
    at AST_ClassMethod.eval [as _codegen] (eval at <anonymous> (tools\node.js:18:1), <anonymous>:21709:9)
    at doit (eval at <anonymous> (tools\node.js:18:1), <anonymous>:21081:18)
    at AST_ClassMethod.eval [as print] (eval at <anonymous> (tools\node.js:18:1), <anonymous>:21074:13)
    at eval (eval at <anonymous> (tools\node.js:18:1), <anonymous>:22129:22)
    at Array.forEach (<anonymous>)
    at eval (eval at <anonymous> (tools\node.js:18:1), <anonymous>:22123:19)
    at OutputStream.with_indent (eval at <anonymous> (tools\node.js:18:1), <anonymous>:20830:26)
    at Object.with_block (eval at <anonymous> (tools\node.js:18:1), <anonymous>:20857:9)
```
Thanks for the further analysis &minus; that matches my observation with the test case in #5664
```console
$ uglifyjs issue_5662.js --compress
ERROR: self.name.print is not a function
    at print_lambda (eval at <anonymous>...
```
this also shows AST validation catching potentially hidden `uglify-js` bugs 😎
Thank you for so quick fix.
Patch released in `uglify-js@3.17.1`



Patch:
diff --git a/lib/compress.js b/lib/compress.js
index 0bcd468e0e9..99543fc984e 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -6278,21 +6278,24 @@ Compressor.prototype.compress = function(node) {
                 var call = stat.value;
                 if (!call || call.TYPE != "Call") break;
                 if (call.is_expr_pure(compressor)) break;
-                var fn = call.expression;
-                if (fn instanceof AST_SymbolRef) {
-                    if (self.name && self.name.definition() === fn.definition()) break;
-                    fn = fn.fixed_value();
+                var exp = call.expression, fn;
+                if (!(exp instanceof AST_SymbolRef)) {
+                    fn = exp;
+                } else if (self.name && self.name.definition() === exp.definition()) {
+                    break;
+                } else {
+                    fn = exp.fixed_value();
                 }
                 if (!(fn instanceof AST_Defun || fn instanceof AST_Function)) break;
                 if (fn.rest) break;
                 if (fn.uses_arguments) break;
-                if (fn === call.expression) {
+                if (fn === exp) {
                     if (fn.parent_scope !== self) break;
                     if (!all(fn.enclosed, function(def) {
                         return def.scope !== self;
                     })) break;
                 }
-                if (fn.name
+                if ((fn !== exp || fn.name)
                     && (parent instanceof AST_ClassMethod || parent instanceof AST_ObjectMethod)
                     && parent.value === compressor.self()) break;
                 if (fn.contains_this()) break;
@@ -6321,7 +6324,7 @@ Compressor.prototype.compress = function(node) {
                         fn.argnames.push(fn.make_var(AST_SymbolFunarg, fn, "argument_" + len));
                     } while (++len < self.argnames.length);
                 }
-                return call.expression;
+                return exp;
             }
             break;
         }


Test patch:
diff --git a/test/compress/classes.js b/test/compress/classes.js
index 32c4d74eebd..f2e6e1860b6 100644
--- a/test/compress/classes.js
+++ b/test/compress/classes.js
@@ -3586,3 +3586,36 @@ issue_5531_3: {
     expect_stdout: "foo"
     node_version: ">=16"
 }
+
+issue_5662: {
+    options = {
+        inline: true,
+        reduce_vars: true,
+    }
+    input: {
+        console.log(new (function() {
+            var g = function(a) {
+                return a;
+            };
+            return class {
+                h(b) {
+                    return g(b);
+                }
+            };
+        }())().h("PASS"));
+    }
+    expect: {
+        console.log(new (function() {
+            var g = function(a) {
+                return a;
+            };
+            return class {
+                h(b) {
+                    return g(b);
+                }
+            };
+        }())().h("PASS"));
+    }
+    expect_stdout: "PASS"
+    node_version: ">=6"
+}
diff --git a/test/reduce.js b/test/reduce.js
index 696914bd8ee..d415e71f1c5 100644
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -466,7 +466,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
             }
         }
         else if (node instanceof U.AST_VarDef) {
-            if (node.value && !(parent instanceof U.AST_Const)) {
+            if (node.value && !(node.name instanceof U.AST_Destructured || parent instanceof U.AST_Const)) {
                 node.start._permute++;
                 CHANGED = true;
                 return new U.AST_VarDef({