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);
};
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);
});
};