JavaScript – Is eval the defmacro of JavaScript?

javascriptlisp

In Common Lisp, defmacro basically allows us to build our own DSL.

I read this page today and it explains something cleverly done:

But I wasn't about to write out all these boring predicates myself, so I defined a function that, given a list of words, builds up the text for such a predicate automatically, and then evals it to produce a function.

Which just looks like defmacro to me.

Is eval the defmacro of javascript? Could it be used as such?

Best Answer

It can, but it's more dangerous.

defmacro has three main features:

  1. It runs at compile time;
  2. It is a genuine in-place substitution; and
  3. Because of the whole nature of Lisp syntax, it works at an AST level, not a source-code level.

By contrast, eval:

  1. Executes at runtime;
  2. Is not an in-place substitution; and
  3. Works with strings.

Why does this matter?

First, eval has a speed penalty: because it has to evaluate its arguments every single time it's called, instead of just once at compile time, you'll have a speed hit on each run of the program.

Second, there is no compile-time validation that the eval is actually getting passed something sane. You can have a horrible syntax error in your eval argument (or, worse, a horrible syntax error only when called in certain ways), but it won't be caught until runtime far down the road. Because Lisp macros are compile-time, you can't have that. Your macro might not work, mind you, but it'll compile by definition to something valid.

Third, eval has serious safety issues compared to defmacro. Because Lisp macros work with the AST, there's a high degree of safety. No matter what argument you supply the macro at runtime, those arguments can't allow a malicious caller to "escape" the macro, or call malicious code. That's not true of eval: if you don't think through your string escapes very, very carefully, and you allow any form of user-generated input into the eval string, you're risking allowing malicious code to run.

Okay, but, whatever, I can hear you say. The user would have to do that to himself, and it's already his browser, so why care?

Because there are lots of ways to inject scripts in that situation. Cross-site script requests, for example, or forgetting to escape a string properly that you store in a database. There are lots of ways of getting malicious values into those macros--and the moment that happens, your users' data is toast.

Finally, even if all of that doesn't dissuade you, JavaScript's eval in particular is simply less powerful. There are odd ways that it interacts with the enclosing variable scope and the global variable scope, for example, and there's no sane way to do the equivalent of Common Lisp's gensym for variable hygiene.

Can you use eval in some of the places you might use defmacro? Sure. But they are hardly interchangeable, and I hope that the above explanation makes it clear why absolutely zero good JavaScript frameworks I know use eval.

Instead, if you really find yourself wanting macros, go find a compiles-to-JavaScript language that has them, such as ClojureScript. That way, you'll get the benefits of Lisp macros without the drawbacks of eval.

Related Topic