Uglifyjs: Uglify drops arguments to functions that use `arguments` object

Created on 14 Jun 2017  路  24Comments  路  Source: mishoo/UglifyJS

Bug report or feature request?: Bug report

ES5 or ES6+ input?: ES5

Uglify version (uglifyjs -V): uglify-es 3.0.15

JavaScript input

(function() {
function sum() {
  var s = 0;
  for (var i = 0, n = arguments.length; i < n; ++i) {
    s += arguments[i];
  }
  return s;
}

console.log(sum(1, 2, 3, 4));
console.log(sum(5, 6, 7, 8));
}());

The uglifyjs CLI command executed or minify() options used. uglify.minify(code);

JavaScript output or error produced. (beautified for convenience)

!function() {
    function o() {
        for (var o = 0, n = 0, l = arguments.length; n < l; ++n) o += arguments[n];
        return o;
    }
    console.log(o()), console.log(o());
}();

the calls to sum (or o in the minified version) must not drop the arguments, as these are accessed via the arguments object inside of the function.

bug

All 24 comments

Only affects harmony:

$ cat test.js
(function() {
    function sum() {
        var s = 0;
        for (var i = 0, n = arguments.length; i < n; ++i) {
            s += arguments[i];
        }
        return s;
    }
    console.log(sum(1, 2, 3, 4));
    console.log(sum(5, 6, 7, 8));
})();

$ uglifyjs -V
uglify-js 3.0.16

$ uglifyjs test.js -cb bracketize
!function() {
    function sum() {
        for (var s = 0, i = 0, n = arguments.length; i < n; ++i) {
            s += arguments[i];
        }
        return s;
    }
    console.log(sum(1, 2, 3, 4)), console.log(sum(5, 6, 7, 8));
}();

$ uglifyjs -V
uglify-es 3.0.16

$ uglifyjs test.js -cb bracketize
!function() {
    function sum() {
        for (var s = 0, i = 0, n = arguments.length; i < n; ++i) {
            s += arguments[i];
        }
        return s;
    }
    console.log(sum()), console.log(sum());
}();

Regression introduced in 68138f22810d4a2086569e979f640202adf757d3

@kzc I'm confused. I've reverted the one liner in the patch, and the result is still the same as https://github.com/mishoo/UglifyJS2/issues/2097#issuecomment-308484593?

Edit: backing up further...

$ uglifyjs -V
uglify-es 3.0.10

$ uglifyjs test.js -cb bracketize
!function() {
    function sum() {
        for (var s = 0, i = 0, n = arguments.length; i < n; ++i) {
            s += arguments[i];
        }
        return s;
    }
    console.log(sum()), console.log(sum());
}();

I was wrong about the regression comment above. Got the branches mixed up.

No worries - let me handle this one while you work on await :wink:

(Except I bet I'm holding you up with #2095 :sweat_smile:)

This harmony bug is an old one:

$ git checkout harmony-v3.0.0
HEAD is now at 3fac29a... Merge pull request #1876 from alexlamsl/harmony-v3.0.0
$ cat 2097.js | bin/uglifyjs -c | node
0
0

(Except I bet I'm holding you up with #2095 馃槄)

A little bit, but not a problem.

Getting closer...

$ git checkout harmony-v2.8.10
Previous HEAD position was c7063c1... Merge pull request #1591 from alexlamsl/harmony-v2.8.11
HEAD is now at e9920f7... v2.8.10

$ cat 2097.js | bin/uglifyjs -c | node
10
26
$ git checkout harmony-v2.8.11
Previous HEAD position was e9920f7... v2.8.10
HEAD is now at c7063c1... Merge pull request #1591 from alexlamsl/harmony-v2.8.11

$ cat 2097.js | bin/uglifyjs -c | node
0
0

whoa guys, thank you for the quick investigation. Very impressive!

Proof that I don't know how to use GitHub - this mess does not really give me useful information :confused:

https://github.com/mishoo/UglifyJS2/compare/harmony-v2.8.10...harmony-v2.8.11

Likely related to how this commit was applied to harmony: b633706ce42576b7e2aa85a96c5691bde87e71ac

@kzc thanks for narrowing this down - investigating.

@davidaurelio Workaround is to disable reduce_vars:

$ bin/uglifyjs -V
uglify-es 3.0.15
$ cat 2097.js | bin/uglifyjs -c reduce_vars=true | node
0
0



md5-05f5a29247cc0164053882f76a5cef9c



$ cat 2097.js | bin/uglifyjs -c reduce_vars=false | node
10
26

thank you!

Hmm - looks like uses_argument is broken on harmony:

--- a/lib/compress.js
+++ b/lib/compress.js
@@ -4058,6 +4058,7 @@ merge(Compressor.prototype, {
             var d = self.definition();
             var fixed = self.fixed_value();
             if (fixed instanceof AST_Defun) {
+console.error(fixed.TYPE, fixed.uses_arguments, fixed.print_to_string());
                 d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
             }
             if (compressor.option("unused")
$ uglifyjs -V
uglify-es 3.0.16

$ uglifyjs test.js -cb bracketize
Defun false function sum(){for(var s=0,i=0,n=arguments.length;i<n;++i)s+=arguments[i];return s}
!function() {
    function sum() {
        for (var s = 0, i = 0, n = arguments.length; i < n; ++i) {
            s += arguments[i];
        }
        return s;
    }
    console.log(sum()), console.log(sum());
}();

$ uglifyjs -V
uglify-js 3.0.16

$ uglifyjs test.js -cb bracketize
Defun true function sum(){for(var s=0,i=0,n=arguments.length;i<n;++i)s+=arguments[i];return s}
!function() {
    function sum() {
        for (var s = 0, i = 0, n = arguments.length; i < n; ++i) {
            s += arguments[i];
        }
        return s;
    }
    console.log(sum(1, 2, 3, 4)), console.log(sum(5, 6, 7, 8));
}();

Hmm - looks like uses_argument is broken on harmony:

Yikes - that'd do it.

Begs the question - do uses_eval and uses_with work on harmony?

Proof that I don't know how to use GitHub - this mess does not really give me useful information
harmony-v2.8.10...harmony-v2.8.11

I've never satisfactorily found a way to list just the commits on a specific branch in git.

Those multi-commit merges from master to harmony throw things for a loop.

I ended up staring at #1591 for a bit, but quickly got bored and started poking at the code directly.

Reduced case with patch from https://github.com/mishoo/UglifyJS2/issues/2097#issuecomment-308499525:

$ echo 'function f(){arguments}f();' | uglifyjs -c toplevel
Defun true function f(){}


$ echo 'function f(){for(;arguments;);}f();' | uglifyjs -c toplevel
Defun false function f(){for(;arguments;);}
!function(){for(;arguments;);}();

Looks like a fix is required for lib/scope.js

This is the culprit: https://github.com/mishoo/UglifyJS2/commit/266ddd96399afcdee56d9d58b287f912b8728342

I believe it should read if (sym.scope instanceof AST_Lambda && name == "arguments") {, though I wonder if that breaks master - oh wait, there is catch on master which uses block scope :smiling_imp:

Bingo! This works on master:

$ echo 'function f(){console.log(arguments[0])}f(1)' | uglifyjs -c toplevel
!function(){console.log(arguments[0])}(1);

... but this fails:

$ echo 'function f(){try{throw 0}catch(e){console.log(arguments[0])}}f(1)' | uglifyjs -c toplevel
!function(){try{throw 0}catch(e){console.log(arguments[0])}}();

$ echo 'function f(){try{throw 0}catch(e){console.log(arguments[0])}}f(1)' | node
1

$ echo 'function f(){try{throw 0}catch(e){console.log(arguments[0])}}f(1)' | uglifyjs -c toplevel | node
undefined

Another repro - the presence of the inner block matters.

$ echo 'console.log(function() {{ return arguments[0]; }}("Keyser Soze"))' | uglifyjs -c
console.log(function(){return arguments[0]}());

@robpalme yes, since ES6 introduces the concept of block scopes, it exposes the otherwise (almost) innocuous bug in https://github.com/mishoo/UglifyJS2/commit/266ddd96399afcdee56d9d58b287f912b8728342

Please feel free to try out #2099 on uglify-es and see if you can break it.

2099 appears to handle it:

$ echo 'console.log(function() {{ return arguments[0]; }}("Keyser Soze"))' | bin/uglifyjs -c
console.log(function(){return arguments[0]}("Keyser Soze"));
Was this page helpful?
0 / 5 - 0 ratings

Related issues

buu700 picture buu700  路  5Comments

PinkyJie picture PinkyJie  路  3Comments

Havunen picture Havunen  路  5Comments

alexlamsl picture alexlamsl  路  5Comments

GrosSacASac picture GrosSacASac  路  3Comments