Links

pmuellr is Patrick Mueller

other pmuellr thangs: home page, twitter, flickr, github

Tuesday, November 03, 2009

evil eval

With all the nascent alt-JS things people are playing with, talked about on this InfoQ blog post, I got to thinking a while back how we could make debugging a little better for these folks in WebKit's Web Inspector.

With my familiarity of Java's innards, I happened to think about supporting a mode of using an alternative source file, and line number mappings between the original source and the generated source, to allow folks to have some level of source-level debugging in their original language. That would be for systems which actually generate JavaScript from the original language.

Java supports meta-data in it's .class files for this sort of information, and I've seen it used for things like debugging Groovy in Eclipse using the stock Java debugger. Not a perfect story, but useful.

So I opened up Bug 30933 - Web Inspector: support for debugging alt-JS files which generate JS. Feel free to drop ideas there.

Anyhoo ...

While chatting with someone who could make use of this, they noted that their biggest problem was that eval() doesn't do a very good job of providing information about syntax errors in the eval()'d code. Really? Time for some experiments.

First, why care about eval() in the first place? Since it's evil. If code is being generated from some alternate source, don't you end up with JavaScript code you can just use in the browser with a <script> element? Sure, you can do it that way. But why do an off-line compile if you could do the compile in the browser?

It's also the case that some 'traditional' JavaScript libraries like Dojo may end up (depending on your configuration), downloading your source via XHR and then running it via eval() instead of using <script> elements.

Getting good diagnostic information from eval() would be good for lots of people.

On to the experiment:

Here's the source of an HTML file to try out with different browsers. There's a link in the HTML which, when clicked, will run the clickMe() JavaScript function. That function builds a string which will be passed into eval(). I added 100 or so newline's in the source before a string that all the browsers would actually choke on (took me a minute; my first guesses at things that would cause syntax errors didn't). The 100 empty lines are to see what kind of line numbers JavaScript will tell us about, if at all; if line numbers with syntax errors are reported, we want to see a line number like 100, not 13 (the line in the source that invokes eval()).

The page is also available on my web site.

Below the source are the results you see in the browser after clicking; these are the properties of the exception object generated by eval(), one per line.

HTML source for test:

     1  <html>
     2  <head>
     3  </head>
     4  <body>
     5  <a href="javascript:clickMe();">click me!</a>
     6  <p>
     7  <div id="output" style="font-family: monospace"></div>
     8  <script>
     9  function clickMe() {
    10      var lines99 = new Array(100).join("\n");
    11      evalString = lines99 + "for ]](x does not compute)]]\n"; 
    12      try {
    13          eval(evalString); // this is line 13 of the source file
    14      }
    15      catch(e) {
    16          dumpException(e);
    17      }
    18  }
    19  
    20  function dumpException(obj) {
    21      var result = "Exception:<br>\n";
    22      for (var key in obj) {
    23          result += "   " + key + ": " + obj[key] + "<br>\n";
    24      }
    25      document.getElementById("output").innerHTML = result;
    26  }
    27  </script>
    28  </body>
    29  </html>

WebKit nightly r50423 on Mac (basically, Safari):

Exception:
   message: Parse error
   line: 100
   sourceId: 4837249224
   name: SyntaxError

Opera 10.00 Build 6652 on Mac:

Exception:
   message: Statement on line 6: Syntax error stacktrace: n/a; see opera:config#UserPrefs|Exceptions Have Stacktrace
   opera#sourceloc: 6
   stacktrace: false

Google Chrome 4.0.223.11 on Mac:

Exception:
   message: Unexpected token ]
   stack: SyntaxError: Unexpected token ]
    at clickMe (http://muellerware.org/exception-in-eval.html:12:8)
    at unknown source
   type: unexpected_token
   arguments: ]
   name: SyntaxError

Firefox 3.5.4 on Mac:

Exception:
   message: missing ( after for
   fileName: http://muellerware.org/exception-in-eval.html
   lineNumber: 112
   stack: eval("[100 \n's elided]for ]](x does not compute)]]\n")@:0 clickMe()@http://muellerware.org/exception-in-eval.html:13 @javascript:clickMe();:1
   name: SyntaxError

IE 8.0.7100.0 on Windows 7 RC:

Exception:
   name: SyntaxError
   message: Expected '('
   number: -2146827283
   description: Expected '('

Google Chrome 3.0.195.27 on Windows 7 RC:

Exception:
   message: Unexpected token ]
   stack: SyntaxError: Unexpected token ] at clickMe (http://muellerware.org/exception-in-eval.html:13:8) at unknown source
   type: unexpected_token
   arguments: ]
   name: SyntaxError    

Yikes, not a pretty picture! WebKit was the only one that got the line number I wanted. But it gave a pretty crappy "message". Nice seeing the "stack" entries for some of the implementations.

To be honest, things like the "message" - the reason why you got the syntax error - I expect to be somewhat implementation dependent. Although I expect something (looking at you, JavaScriptCore!).

There's a question of semantics, of course. The exception generated from WebKit, with the line number of the eval()'d source, would be confusing had I not caught the exception (though perhaps I'd see something different in the debugger had I not caught the exception). In reality, what you may really want to see, structurally, is something like Java's nestable InvocationTargetException, for eval(), because there are multiple levels of things going on here; for the ultimate evil, eval() may end up doing some of it's own eval()'s.

In any case, it would be nice to see some of this stuff standardized. In ECMAScript 6, I guess.

1 comment:

Unknown said...

I would like ES to give me an additional line number **relative** to the eval'd script. I.e. line number of the eval() statement, and line number within the string eval'uated.