Is eval evil? Just-in-time compiling
wanago.io
wanago.io
https://jsfiddle.net/matthewaveryusa/e6EKV/6/
I could have done this without an eval if I decided to write an interpreter. Instead I whitelisted a subset of javascript and evaled it -- I believe the code is simpler with the eval than if I were to write a parser + interpreter.
I challenge anyone to escape my whitelisting :)
I'm also pretty sure the Google Closure library originally used whitelist checking + eval to parse JSON before JSON.stringify was a thing.
I still haven't been able to do complete code execution yet, but have reasons to believe it might be possible.
Simply using `x1` as input causes it to print "deadbeef11 is not defined". Additionally, parens are allowed, and there is the `=>` arrow syntax for defining functions. Thus, we have a way to define variables, a way to define lambda abstractions, as well as a way to perform applications. Therefore, we have the untyped lambda calculus. This is Turing complete, so I have reason to believe that it should be possible to execute arbitrary code. I just need to find some time to do so :)
Update: `(x1=>x1(x1))(x1=>x1(x1))` gives "Too much recursion" :D
const eval2 = eval
eval2(...)
That put’s it back in global scope, even when assigned within a closure with access to a local variable. The local variable will be undefined, rather than it’s actual value, even when called next to the regular eval(). Odd, I know.Here’s a quote:
"any invocation of eval that is not a direct call uses the global environment as its variable environment rather than the caller’s variable environment." ECMAScript 5 standard,
For example a string can be created like this:
(x=>0)+0
If arbitrary code can be constructed in your environment ... hard to say. But as jaybosamiya has shown, it is at least possible to escape your error handling.
I work on a Ruby implementation that optimises through eval just fine.
The "safe" and fast way to use eval for JIT is to get it to return a function, with all user inputs as parameters (like like the safe and fast way to do SQL is with parameters):
var f = eval("(function (a, b) { return a + b })");
f("a", "b");var f = new Function('a', 'b', 'return a + b')
eval("some code { " + stuff + "} more code")
and runs the parser enough to observe that `stuff` fills in a scope about which it is possible to reason even without knowing about what `stuff` is. In some cases, this will get really lucky: eval("some code blah blah object." + field + " more things")
and you can imagine this being turned into object[field] anytime `field` in this expression is an identifier.Basically, in this world, you'd be able to distill out the long-lived parts of an eval string, and the JIT(s) can focus on optimizing that.
So assume it doesn’t and fixit up if it ever does. That’s the basics of JIT compilation!
One reason is that it makes it significantly harder to reason about the performance of a program, because the behavior of the JIT is based on all kinds of internal state and pattern-matching rules, which are typically hard to expose in a comprehensible way. The more complexity you add to the optimization and deoptimization process, the more inscrutable the performance characteristics become.
Another reason is that it encourages developers to use unsafe patterns. If programmers have a choice between `obj[key]` and `eval("obj." + key)`, why would you put effort into optimizing the one that has the potential to blow up if "key" has funny characters, when a safer, cleaner alternative exists?
Number 1: you say "eval is optimizeable", but optimizing eval in general is incredibly difficult. It's true that you can add optimizations for specific situations. But every time you do so, you expand the surface area of the boundary between "efficient" and "inefficient", and it becomes harder to maintain a mental model of how any given chunk of code will be executed. (For example, in Python, strings are immutable; when concatenating strings in a loop, the intermediate copies can be optimized away but only sometimes, which means that pattern is generally avoided as a matter of good coding style. Tail-return optimization in Scheme is a useful trick that suffers from similar drawbacks, which is probably why you don't see it implemented in a lot of other languages.)
I'm skeptical that adding this type of complexity results in a better overall developer experience. Remember, code is written not just for the compiler, but also to be read and understood.
And number 2: in practice, most languages don't have multiple competing implementations at the level of complexity you're talking about. When your compiler gets to the point of doing sophisticated JIT optimizations, maintaining it requires a lot of specialized knowledge and experience. It's impractical to fork a project like V8 or Hotspot to add a specific new optimization. So individual runtime environments usually aren't subject to competitive pressure, except from entirely different languages.
It doesn't matter if eval is optimizeable in the general case. It only matters if this speeds up some cases without hurting others, on average. VMs and compilers are great at doing such optimizations already.
You don't have to fork V8 since there are other JavaScript implementations. I recommend JavaScriptCore because it's the fastest.
Similarly, Ruby, Python, and Lua all have multiple competing implementations and some folks pick them based on performance. Java is special, but that's because HotSpot emerged as the victor in a brutal multi-implementation battle for Java performance supremacy. Java used to have many implementations that all contributed something to our modern knowledge of VM design and this wouldn't have happened without the competition.
Therefore, I think you're wrong on both numbers. Number 1 - this is not a semantically visible optimization; it just makes programs take less time on average; Number 2 - in practice, languages absolutely do have multiple competing implementations.
Basically you are trying to say we want to discover some kind of semantic structure from just a prefix of the textual form of the code. We can do a partial parse and see what's possible right after the string literal; and if there's only one possibility, we JIT it. But without full code, we can't really do much JIT at all.
When I started I had this voice in my head whispering again and again "eval is evil, eval is evil". Then I just got over it...
I ended up with one-pass, event triggered, construct-and-run (essentially "eval") solution. The user writes simple code that is event triggered. The code is converted into a string, rewritten in realtime and "enhanced". An Asynchronous Function Constructor then converts it back to a function and it gets invoked by the triggering event.
It works great and though there is, obviously, a performance penalty for the realtime rewriting, it is never the limiting performance factor for implementations (multiple DOM element animation is).
Examples:
https://www.blocklike.org/example/12-advanced/recursion_tree...
https://www.blocklike.org/example/12-advanced/color_bubbles_...
https://www.blocklike.org/example/14-games/avoid.html
Repo:
A kind of filter shown in the matthewaveryusa's reply may be secure, but it's pretty limiting, and may happen to be too limiting for a particular purpose.
It would be great if the AST of the expression to be evaluated could be checked. That is, `eval` could be two pieces: "compile" and "execute" phases; this is how it works in Python.
Another tool could be a type system that marks user input as "tainted" and helps you track where you're using user input in any capacity. But languages featuring `eval` usually lack such a type system.
If you're an experienced programmer who understands the risks of eval and there's no other good options, then by all means use it, but for other cases "eval is evil".
https://github.com/pygy/LuLPeg https://github.com/pygy/strung.lua
More generally, generating code is used for a lot of regex engines, though it may happen before runtime.
https://github.com/rogual/mxd/blob/master/Matrix.js#L50
It benchmarked pretty well back when I wrote it, though I haven't checked against current JS engines to see if it's still a performance win.
The article even mentions JSON.parse which itself relied on eval.
Does JSON.parse still rely on eval under the hood?
Regardless... a wildly popular method used to propagate the most popular data format relied on eval. At least for a time. Nothing evil happened as a result.
In another life I did PHP shells. PHP (which has a weird relationship with JIT) had so many ways to use eval, without actually typing eval. One of the weird ways that was only removed in PHP 7 was the preg_replace function, with the e flag; which evals any php expression, outside the scope of the function.
My point is that if there is eval used in code; it either is meant for temporary use (because devs are lazy,) or the code needs to be done differently to support the desired outcome.
Saying eval is just confusing the concept with the implementation. There's no evidence that metaprogramming is "bad" unless you are ready to define "bad". Might as well say pointers are "bad" in the same vein.
I can think of a few good use cases (though outside the context of JIT). Consider something like Jinja where you can put python code in your template - we've used this to generate C++ source files from JSON metadata in our embedded systems products. In this case it's pretty safe because both the producer and consumer of the eval is the same person.
Eval'ing during macroexpansion in Gambit Scheme is the basis for my book, http://billsix.github.io/bug.html#_computation_at_compile_ti...
It allows me to make a compile-time unit test framework in 7 lines of code