minify icon indicating copy to clipboard operation
minify copied to clipboard

Mangle redeclares/reuses the same variable name that's in outer scope

Open zhanzhenzhen opened this issue 7 years ago • 3 comments

My code was compiled to:

Outermost scope:

(function() {
    var e = Math.ceil
      , n = String.fromCharCode
      , r = Math.min
      , l = Math.pow
      , o = Math.PI
      , h = Number.isInteger
      ...

Middle scope:

var o, r, l, d, y, m, h, ...
...
h = void 0 === (ve = n.fillMode) ? "both" : ve,
...

Inner scope:

...
0 <= ["forwards", "both"].indexOf(h) ? (()=>(r = h(y) && !n
...

When running in browser, it threw the error:

Uncaught TypeError: h is not a function

(Since this is a private repo I couldn't disclose more of my code)

zhanzhenzhen avatar Aug 28 '18 14:08 zhanzhenzhen

Can you try with builtIns: false and get back.

vigneshshanmugam avatar Sep 25 '18 20:09 vigneshshanmugam

I'm sorry. I've already switched to "terser" to do minification. It's difficult to trigger the condition that causes this bug. My code has changed a lot since the report.

zhanzhenzhen avatar Sep 25 '18 20:09 zhanzhenzhen

Got the same error.

Describe the bug

Function parameter and outer scope variable names mangled into the same literal, hence parameter erroneously shadows variable. Bug happens when plugin babel-plugin-minify-mangle-names used with option keepFnName: true.

To Reproduce

input code - 110 lines (bug disapperars if further isolation attempts taken)

var scope_0;
(function (scope_0) {
    var scope_1;
    (function (scope_1) {
        SomeFn({
            method5: function (param30) {
                var _this = this;
                var me = this, xVar0 = me.model, xVar1 = [], xVar2 = me.field0;
                (function () { return __awaiter(_this, void 0, void 0, function () {
                    var _a, xVar3, xVar4, xVar5, xVar6, xStart, xVar7, xVar8, xVar9, r, xVar10, c, xVar11, _b, _c;
                    return __generator(this, function (_d) {
                        switch (_d.label) {
                            case 0:
                                if (!!(me.xVar8 && me.xVar7)) return [3, 2];
                                return [4, scope_0.method0(xVar2, function (field0) { return [field0.field5,
                                        scope_0.method1(field0.field1, scope_1.SomeFn1)
                                    ]; })];
                            case 1:
                                (_d.sent())(function (field0) {
                                    me.field1 = field0.field5;
                                    var xFields = xVar0.method2(), xFieldNames = me.field4 || xFields.map(function (field) { return field.getName(); });
                                    var xVar19 = scope_1.SomeFn3(xFieldNames, field0.field1);
                                    var xVar8 = me.xVar8 = [], xVar7 = me.xVar7 = [];
                                    for (var i = 0, xCount = xVar19.length; i < xCount; i++) {
                                        var xVar20 = xVar19[i];
                                        if (xVar20 < 0)
                                            continue;
                                        var xName = xFieldNames[i], xField = xFields.find(function (field) { return field.getName() === xName; });
                                        xVar7.push(xName);
                                        xVar8.push(StaticGetter.Get(xField && xField.getType())(xVar2, xVar20));
                                    }
                                });
                                return [3, 4];
                            case 2:
                                if (!!me.field3) return [3, 4];
                                _a = me;
                                return [4, xVar2.field5];
                            case 3:
                                _a.field1 = _d.sent();
                                _d.label = 4;
                            case 4:
                                xVar3 = (me.field6 == null) ? me.field1 : Math.min(me.field6, me.field1), xVar4 = 0, xVar5 = xVar3;
                                xVar6 = param30.getLimit(), xStart = param30.getStart();
                                if (me.enablePaging && xStart !== undefined && xVar6 !== undefined) {
                                    xVar4 = xStart;
                                    if (xVar4 + xVar6 < xVar5)
                                        xVar5 = xVar4 + xVar6;
                                }
                                xVar7 = me.xVar7, xVar8 = me.xVar8, xVar9 = xVar8.length;
                                Debug.assert(xVar7.length == xVar8.length);
                                Debug.assert(xVar7.every(function (name) { return !!name; }));
                                Debug.assert(xVar8.every(function (getter) { return !!getter; }));
                                r = xVar4;
                                _d.label = 5;
                            case 5:
                                if (!(r < xVar5)) return [3, 11];
                                xVar10 = {};
                                c = 0;
                                _d.label = 6;
                            case 6:
                                if (!(c < xVar9)) return [3, 9];
                                xVar11 = xVar8[c];
                                _b = xVar10;
                                _c = xVar7[c];
                                return [4, xVar11(r)];
                            case 7:
                                _b[_c] = _d.sent();
                                _d.label = 8;
                            case 8:
                                c++;
                                return [3, 6];
                        }
                    });
                }); })().done(function () {
                    param30.setSuccessful(true);
                }, function (e) {
                    param30.setException(e);
                });
                return {
                    param30: param30
                };
            }
        });
        var StaticGetter = (function () {
            function StaticGetter() {
            }
            StaticGetter.string = function (field0, param40) {
                var xValue = new Out();
                return function (row) {
                    return field0.method6(row, param40, xValue) ? xValue.value : undefined;
                };
            };
            StaticGetter.auto = function (field0, param40) {
                return function (row) {
                    var xValue = field0.AsVariant(row, param40);
                    return (xValue !== undefined && xValue !== null) ? xValue : undefined;
                };
            };
            StaticGetter.Get = function (type) {
                return this[SomeFn5(type)] || StaticGetter.auto;
            };
            return StaticGetter;
        }());
        scope_1.StaticGetter = StaticGetter;
        function SomeFn5(type) {
            return type ? (typeof type === "string" ? type : type.type) : "auto";
        }
        scope_1.SomeFn5 = SomeFn5;
    })(scope_1 = scope_0.scope_1 || (scope_0.scope_1 = {}));
})(scope_0 || (scope_0 = {}));


Actual Output

output - 86 lines, prettified

var scope_0;
(function(a) {
    var b;
    (function(b) {
        function SomeFn5(a) {
            return a ? "string" == typeof a ? a : a.type : "auto"
        }
        SomeFn({
            method5(d) {
                var e = this,
                    f = this,
                    g = f.model,
                    h = f.field0;
                return function() {
                    return __awaiter(e, void 0, void 0, function() {
                        var e, i, j, k, l, m, n, o, p, q, s, t, u, v, w;
                        return __generator(this, function(c) {
                            switch (c.label) {
                                case 0:
                                    return f.xVar8 && f.xVar7 ? [3, 2] : [4, a.method0(h, function(c) {
                                        return [c.field5, a.method1(c.field1, b.SomeFn1)]
                                    })];
                                case 1:
                                    return c.sent()(function(a) {
                                        f.field1 = a.field5;
                                        for (var d, e = g.method2(), j = f.field4 || e.map(function(a) {
                                                return a.getName()
                                            }), k = b.SomeFn3(j, a.field1), l = f.xVar8 = [], m = f.xVar7 = [], n = 0, o = k.length; n < o; n++)
                                            if (d = k[n], !(0 > d)) {
                                                var p = j[n],
                                                    q = e.find(function(a) {
                                                        return a.getName() === p
                                                    });
                                                m.push(p), l.push(c.Get(q && q.getType())(h, d))
                                            }
                                    }), [3, 4];
                                case 2:
                                    return f.field3 ? [3, 4] : (e = f, [4, h.field5]);
                                case 3:
                                    e.field1 = c.sent(), c.label = 4;
                                case 4:
                                    i = null == f.field6 ? f.field1 : Math.min(f.field6, f.field1), j = 0, k = i, l = d.getLimit(), m = d.getStart(), f.enablePaging && void 0 !== m && void 0 !== l && (j = m, j + l < k && (k = j + l)), n = f.xVar7, o = f.xVar8, p = o.length, Debug.assert(n.length == o.length), Debug.assert(n.every(function(a) {
                                        return !!a
                                    })), Debug.assert(o.every(function(a) {
                                        return !!a
                                    })), q = j, c.label = 5;
                                case 5:
                                    if (!(q < k)) return [3, 11];
                                    s = {}, t = 0, c.label = 6;
                                case 6:
                                    return t < p ? (u = o[t], v = s, w = n[t], [4, u(q)]) : [3, 9];
                                case 7:
                                    v[w] = c.sent(), c.label = 8;
                                case 8:
                                    return t++, [3, 6];
                            }
                        })
                    })
                }().done(function() {
                    d.setSuccessful(!0)
                }, function(a) {
                    d.setException(a)
                }), {
                    param30: d
                }
            }
        });
        var c = function() {
            function StaticGetter() {}
            return StaticGetter.string = function(a, b) {
                var c = new Out;
                return function(d) {
                    return a.method6(d, b, c) ? c.value : void 0
                }
            }, StaticGetter.auto = function(a, b) {
                return function(c) {
                    var d = a.AsVariant(c, b);
                    return void 0 !== d && null !== d ? d : void 0
                }
            }, StaticGetter.Get = function(a) {
                return this[SomeFn5(a)] || StaticGetter.auto
            }, StaticGetter
        }();
        b.StaticGetter = c, b.SomeFn5 = SomeFn5
    })(b = a.scope_1 || (a.scope_1 = {}))
})(scope_0 || (scope_0 = {}));


Names `_d` and `StaticGetter` are translated into `c`:

line 17: return __generator(this, function(c) {
line 86: var c = function() {

because of this shadowing call c.Get fails in runtime:

line 34: m.push(p), l.push(c.Get(q && q.getType())(h, d))

Expected Output

Names StaticGetter and _d are translated into different names in output.

Configuration

from package.json:

    "@babel/core": "^7.2.2",
    "@babel/types": "^7.2.2",
    "@babel/preset-env": "^7.2.2",
    "babel-preset-minify": "^0.5.0",
    "gulp": "^3.9.1",
    "gulp-babel": "^8.0.0",

minify config:

{
    "builtIns": false,
    "mangle": {  keepFnName: true }
 }
gulp task
function () {
    return gulp.src(SRC)
        .pipe(babel({ // @see https://github.com/babel/gulp-babel
            generatorOpts: {
                jsescOption: {
                    minimal: true // @see https://github.com/babel/babel/pull/8143
                }
            },
            presets: [
                [
                    "minify", {
                         "builtIns": false,
                         "mangle": { /*keepClassName: true,*/ keepFnName: true }
                     }
                ]
            ],
            comments: false
        }))
        .on("error", function (error) { console.log(error); })
        .pipe(gulp.dest(DEST_DIR));
}

attatrol avatar Jan 23 '19 12:01 attatrol