Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is recursive use of eval() an alright way to inspect the execution of a program?

I've been building a basic live-evaluation javascript development environment (I call it the WEPL.) over the past few days, and realized it'd be nice to be able to associate error messages to line numbers. Unfortunately, eval() doesn't provide a nice way to do this, that I can find.

The solution I've come up with so far is to transform the source before eval() so that it's a set of nested calls to a wrapper to eval() that records some information before eval, checks to see if the eval succeeds, and then uses that info to output more useful troubleshooting information to the user.

My question is, why might this be a bad idea? What problems do I need to solve to make sure this works well?

An example of the sort of transformation I mean, just to make this concrete.

This

if (cond) {
  return foo + bar;
}
else {
  return baz + quux;
}

becomes this

if (myEval('cond')) {
  return myEval("myEval(\"foo\") + myEval(\"bar\")");
else {
  return myEval("myEval(\"baz\") + myEval(\"quux\")");
}

Where I obviously didn't wrap the highest level, though I could've, and the programmatic version would.

like image 679
heartpunk Avatar asked Sep 14 '12 20:09

heartpunk


2 Answers

This won't work if you want to accept even remotely complex scripts. A few potential problems:

Scope

var i = 1; // global scope
!function() {
    var i = 2; // function scope
}();
alert(i); // 1

vs.

myEval('var i = 1;'); // global scope
myEval('!function() {
    myEval(\'var i = 2;\'); // eval has global scope, always
}();');
myEval('alert(i);'); // 2

Closures

!function() {
    var i = 1; // local to outer function
    !function() { // inherits context from outer function
        alert(i); // 1
    }();
}();

vs.

myEval('!function() {
    myEval(\'var i = 1;\'); // local to outer function
    myEval(\'!function() { // eval has global scope; myEval inherits from wherever it was defined
        myEval(\\\'alert(i);\\\'); // undefined
    }();\');
}();');

this

var obj = {
    n: 1,
    f: function() {
        return this.n; // this is the object f is called from
    }
}
alert(obj.f()); // 1

vs.

myEval('var obj = {
    n: myEval(\'1\'),
    f: myEval(\'function() {
           return myEval(\\\'this.n\\\'); // this is always the window in eval
       }')
}');
myEval('alert(obj.f());'); // undefined

Escape creep

You need to escape every quote, and you need to escape escape signs as well. In code with lots of objects, closures, inner functions etc. this will result in escape signs becoming unmanageable:

!function() {
    $(function() {
        $('#foo').click(function() {
            setTimeout(function() {
                $.post('/', function(res) {
                     log(res);
                });
            }, 1000);
        });
    });
}();

(note that this is not a particularly contrived or complicated example, it only involves a delyed action with a callback firing on a certain event)

myEval('!function() {
    myEval(\'$(myEval(\\\'function() {
        myEval(\\\\\\\'$(\\\\\\\'#foo\\\\\\\').click(myEval(\\\\\\\\\\\\\\\'function() {
            myEval(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'setTimeout(myEval(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'function() {
                myEval(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'$.post('/', myEval(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'function(res) {
                     myEval(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'log(res);\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\');
                }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'));\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\');
            }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'), 1000);\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\');
        }\\\\\\\\\\\\\\\'));\\\\\\\');
    }\\\'));\');
}();');

You can probably see what I am getting at.

like image 53
Tgr Avatar answered Oct 16 '22 11:10

Tgr


I had to do something similar when working on an extension for IE. I ended up creating a global variable called 'lineNumber' and I transformed the code to be more like this:

lineNumber = 1; if (cond) {
  lineNumber = 2; return foo + bar;
}
else {
  lineNumber = 5; return baz + quux;
}

Of course, I made to make sure I used curly braces around all blocks and had to keep my coding simple enough to avoid confusing my poor lame parser -- but it got me through it.

like image 2
Jeremy J Starcher Avatar answered Oct 16 '22 11:10

Jeremy J Starcher