New feature: mangle property names

1,490 views
Skip to first unread message

Tim Meadowcroft

unread,
Apr 11, 2011, 10:18:42 AM4/11/11
to ugli...@googlegroups.com

I had a little play and I've written an extra feature for uglifyjs - an option to take a list of property names that will be universally mangled where accessed via literals.

Obviously you couldn't mangle all property names, and if you start passing names of those properties around in variables and function parameters/return values, then it's not going to spot them, but the idea is that by making you name the properties you're happy to mangle, then it's a good way to shorten/obfuscate all those method names and fixed property names in your own code while preserving system names, dynamic names, names of library overrides etc.

And if you don't give it any names to mangle, then it does nothing :)

So given input like

var inner = function() {
    var obj = {
        abc: 123,
        xyz: "abc"
    };
    if ("abc" in obj && obj.hasOwnProperty("abc")) {
       obj["abc"] = 99;
    }
    return obj.abc + obj["xyz"];
};

and an option to mangle property "abc", then it'll rename the "abc" property in the 5 correct spots, but leave the "abc" string (in .xyz) untouched, thus giving us

 var inner = function() {
    var a = {
        p0: 123,
        xyz: "abc"
    };
    "p0" in a && a.hasOwnProperty("p0") && (a.p0 = 99);
    return a.p0 + a.xyz;
};

Of course it has to act globally, but that actually makes it much easier to write :)

I've run it over my large project and it seems to work fine, hiding the names of methods etc. without breaking anything.

I'll offer the code back to the project in a proper form, but the walker routine to do the work is below in case someone can spot anything obvious I've missed... I'm looking for property names in JSON declarations, in accessing properties as ".name" and as "['name']" (including on the left hand side of an assignment), I'm checking the calls to the "in" operator with a string literal on the LHS, and I'm also checking calls to the system methods hasOwnProperty() and isPropertyEnumerable() with string literals.

I've also written a small perl script to report the most frequently occuring property names which I use to then remind myself of names to consider for mangling.

Cheers

--
Tim

function ast_mangle_properties(ast, props) {
    var w = pro.ast_walker(), walk = w.walk, MAP = pro.MAP;
    var mprops = { };
    for (var i = 0; i < props.length; ++i) {
        mprops[props[i]] = "p"+i;
    };
    var mprop = function(p) {
        return (typeof p === "string" && mprops.hasOwnProperty(p)) ? mprops[p] : p;
    };
    return w.with_walkers({
        "sub": function(expr, subscript) {
            // the normal squeeze has already rewritten abc["xyz"] as abc.xyz but just in case...
            if (subscript[0] == "string") {
                return [ "sub", walk(expr), [ "string", mprop(subscript[1]) ] ];
            }
        },
        "dot": function(expr) {
            return [ "dot", walk(expr) ].concat(MAP(slice(arguments, 1), mprop));
        },
        "assign": function(op, lvalue, rvalue) {
            // the default 'assign' walker doesn't walk the lvalue
            return [ "assign", op, walk(lvalue), walk(rvalue) ];
        },
        "binary": function(op, left, right) {
            // check for '"abc" in object'
            if (op === "in" && left[0] === "string") {
                left = ["string", mprop(left[1]) ];
            }
            return [ "binary", op, walk(left), walk(right) ];
        },
        "call": function(expr, args) {
            // Normally as soon as you start passing property names around in variables or as parameters,
            // the game is over, but for 2 special built-in functions we look for string literal parameters
            if (expr[0] === "dot" &&
                (expr[expr.length-1] === "hasOwnProperty" || expr[expr.length-1] === "propertyIsEnumerable")) {
                if (args.length === 1 && args[0][0] === "string") {
                    args = [ ["string", mprop(args[0][1]) ] ];
                }
            }
            return [ this[0], walk(expr), MAP(args, walk) ];
        },
        "object": function(props) {
            return [ "object", MAP(props, function(p){
                var p0 = mprop(p[0]);
                return p.length == 2
                    ? [ p0, walk(p[1]) ]
                    : [ p0, walk(p[1]), p[2] ]; // get/set-ter
            }) ];
        }
    }, function() {
        return walk(ast);
    });
};

Tim Meadowcroft

unread,
Apr 12, 2011, 6:31:45 AM4/12/11
to ugli...@googlegroups.com

This version is now forked at  


if anyone wants to try it out or have a look (specify "--mangle-properties name,name,name...." on the command line) - it could probably do something better with specifying names to mangle and how to mangle them (and I have a version that reports all unique property names it can see and how many times it sees them - good for spotting myPrivateMethodWithVeryLongName() type candidates). 

Of course you wouldn't want to use this feature on something like jQuery (except maybe the few private properties such as _data, _default, _Deferred) but for my large webapp project using ExtJS (lots of derived classes and overrides) where Google's closure compiler is just too hard to make work, this has enabled me to shorten names of lots of my private methods and has shrunk the code by about another 5% as well as helping obscure some of the structures of the code internals.

--
Tim

Jack O'Connor

unread,
Mar 6, 2014, 10:10:16 PM3/6/14
to ugli...@googlegroups.com
This is exactly what I'm looking for. Did this ever make it into UglifyJS?

Tim Meadowcroft

unread,
Mar 7, 2014, 8:33:28 AM3/7/14
to ugli...@googlegroups.com


On Friday, March 7, 2014 3:10:16 AM UTC, Jack O'Connor wrote:
This is exactly what I'm looking for. Did this ever make it into UglifyJS?



As far as I know, no, but you can see it in my fork and the change is here


So you can see that it should be fairly simple to apply a similar patch to the latest version if you wanted to give it a try...

--
T

Jacob Hite

unread,
Mar 29, 2015, 9:08:29 AM3/29/15
to ugli...@googlegroups.com
The latest release of uglify (today) has object property mangling, see v2.4.18. It also supports reserved files for excluding both object properties and variables that you don't want mangled. Check it out.

Tim Meadowcroft

unread,
Mar 29, 2015, 11:20:22 AM3/29/15
to ugli...@googlegroups.com


On Sunday, 29 March 2015 14:08:29 UTC+1, Jacob Hite wrote:
The latest release of uglify (today) has object property mangling, see v2.4.18. It also supports reserved files for excluding both object properties and variables that you don't want mangled. Check it out.



Nice.. but from my brief look at the code, it doesn't seem to spot the use of string literals of property names with the "in" operator, nor the two special function calls that my version also spots and adjusts, namely hasOwnProperty() and propertyIsEnumerable().
Any particular reason for this? The omission of the "in" operator particularly seems an oversight - I'd be interested to know the thinking if this was an explicit decision.

--
Tim
Reply all
Reply to author
Forward
0 new messages