views:

791

answers:

6

Here's the deal, we have a big JS library that we want to compress, but YUI compressor doesn't fully compress the code if it finds an "eval" statement, out of fear that it will break something else. That's great and all, but we know exactly what is getting eval'd, so we don't want it to get conservative because there's an eval statement in MooTools JSON.decode

So basically the question is, is there any alternative (maybe creative) way of writing a expression that returns the eval function? I tried a few, but no dice:

window['eval'](stuff);
window['e'+'val'](stuff);
// stuff runs in the global scope, we need local scope

this['eval'](stuff);
// this.eval is not a function

(new Function( "with(this) { return " + '(' + stuff + ')' + "}"))() 
// global scope again

Any ideas? Thx

+1  A: 
var e = "e";
window[e+"val"](stuff);
elcuco
That doesn't work, things run in the global scope. Also doesn't seem to be portable, webkit issues
Infinity
`window["e"+"val"](stuff)` ?
elcuco
`window.eval === window['eval'] === window['e'+'val']` so no dice.. stuff gets eval'd in the window scope which is not what we need
Infinity
A: 

If possible you may want to try one of the other compression libraries since YUI isn't the only game in town anymore.

Here is a couple articles on the other compression tools available.

Microsoft and Google seem to do a better job than YUI anyways.

Nick Berardi
Thanks for the suggestions.I've played with Google Closure, but it's too aggresive for us, renames too many things and breaks the code. Would require some time to adapt the code just so that it runs, and would probably introduce new bugs.And it would be nice to check out the MS stuff, but we're a Java/OSX shop
Infinity
The trick with Google Closure is to just use the Simple compression, and if that fails just use the Whitespace. Which is a safe alternative, and just removes all the whitespaces, which is better than nothing.
Nick Berardi
That's working with YUI too, it minifies and all that, but it doesn't replace long variable names with shorter ones because of the evals
Infinity
+1  A: 

Could refactor eval calls to some external shim function that is not part of the file being compressed?

recursive
+1  A: 

Not sure if I understood you, but you can apply a function to a specific local (this) scope:

var x = 5;

var f = new Function('alert(this.x)');

function A(x){
    this.x = x;
    f.apply(this,[]);
}

a = new A(10);

This alerts 10 as f is applied with A.this

Miquel
+1  A: 

am i missing something?

var noteval = this.eval; // can be defined before the file is loaded
noteval("alert('not eval. at all');");

(function() {
    console.log(this);
    noteval("alert('chavs!');");
}).bind(window)();

(function() {
    console.log(this);
    noteval("alert('crappy parents');");
}).bind(window.parent)();

check it http://www.jsfiddle.net/nGL79/ with the frames as different eval scopes.

and specific to mootools:

window["ev"+"al"].pass("alert('what');")();
this["ev"+"al"].pass("alert('no!');")(); // local scope too?

and

var noteval = window["ev"+"al"].create({
    bind: this
}); 

hope some of that helps... hope you don't get function eval must be called directly, and not by way of a function of another name though

Dimitar Christoff
Thanks for the ideas, at least now I understand why this.eval wasn't working. Take a look at my own answer
Infinity
+2  A: 

Thanks for all the ideas, I ended up just doing text replacement in the build script that outputs the JS, basically replacing $EVAL$ with eval, after everything has been compressed. I was hoping for a purely JS way, but with so many different eval browser implementations, it's probably better to just leave eval alone

But based on Dimitar's answer and some fiddling around, here's what I found. Seems like the reason why this['eval'] wasn't work is because the place where it's happening, in MooTools JSON.decode, is also a inside a Hash:

var JSON = new Hash({
// snip snip
decode: function(string, secure){
    if ($type(string) != 'string' || !string.length) return null;
    if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;

    return this.eval('(' + string + ')'); // Firefox says: TypeError: this.eval is not a function
}

});

However, if I store the "top level" local scope (all the code, including mootools, runs inside an anonymous function), then it works:

var TOP = this;
var JSON = new Hash({
// snip snip
decode: function(string, secure){
    if ($type(string) != 'string' || !string.length) return null;
    if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;

    return TOP.eval('(' + string + ')'); // All good, things run within the desired scope.
}

});

However this doesn't work in Safari, so bottom line is, what I was trying to do can't be done cross-compatibly. eval is a special touchy function and every browser treats it differently.

Infinity