Is eval evil? Just-in-time compiling(wanago.io)
wanago.io
Is eval evil? Just-in-time compiling
https://wanago.io/2018/11/19/how-does-eval-work-and-how-is-it-evil-javascript-eval/
13 comments
Your whitelisting is quite good!
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
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
That's a really nice example! Your whitelist is pretty restrictive. I was reminded of JSFuck where simply allowing six characters ( ) + [ ] ! would basically allow everything. Although I have to mention, your whitelist still permits undefined (such as x.x) as well as certain strings (0 + /./)
I'm also pretty sure the Google Closure library originally used whitelist checking + eval to parse JSON before JSON.stringify was a thing.
I'm also pretty sure the Google Closure library originally used whitelist checking + eval to parse JSON before JSON.stringify was a thing.
Generally I always use Function() instead of eval, because Function doesn't capture the current scope (and also it won't interfere with JIT optimisation, and avoids possible memory bloat of variables in closure if you keep references to the functions).
I saw a trick where you just do:
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,
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,
Looking throught the list of http://www.jsfuck.com a lot of the 'basics' can be created in your environment too.
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.
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.
Contrary to what the article says, there is no reason whatsoever that code in an eval, even if it uses the calling environment, cannot be optimised.
I work on a Ruby implementation that optimises through eval just fine.
I work on a Ruby implementation that optimises through eval just fine.
Exactly. It's just like SQL: different queries end up as different entries in the query cache; although SQL is beneficially fuzzy in this case, if only the constants differ you should get a cache hit.
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):
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");Eval doesn't have to be bad for JITing. Someone oughta write a JS JIT that interoperates with the parser to look for patterns like:
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.
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.
What if stuff is `} //`?
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.
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.
You just speculate that this doesn’t happen, and bail if it does.
But stuff could contain " }; { " or similar weirdness which means you cannot even make assumptions about the semantics of the code in the constants parts of the string. In the second example there is no reason to use eval in the first place when you can just write object[field], so why optimize for eval support?
> But stuff could contain...
So assume it doesn’t and fixit up if it ever does. That’s the basics of JIT compilation!
So assume it doesn’t and fixit up if it ever does. That’s the basics of JIT compilation!
Yes, in principle, you could add all kinds of micro-optimizations to eval() that preserve its semantic behavior. However, there are some good reasons not to. ("Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should.")
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?
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?
If people use it (they do use eval) and it is optimizeable (eval is optimizeable) then whichever implementation optimizes it will be able to deliver a better overall experience.
I disagree, again for a couple of different reasons.
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.
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.
There is no change to the developer experience when a VM speculates about things like this. Browsers speculate about things that are more complex than this already.
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.
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.
Sure but will you get any benefit to offset the cost? Eval is typically used for dynamically generated code, otherwise why use eval in the first place? Evaling a hardcoded string constant seem completely useless to me so what would be the purpose of optimizing for that use case?
I’ve definitely written uses of eval that have a large amount of stuff that doesn’t change. Pretty sure it’s common.
Last winter I wrote BlockLike.js, an educational library that allows one to use Block programming concepts in JavaScript. This required supporting pacing and waiting while still allowing the user to write simple (and valid) JavaScript.
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:
https://github.com/ronilan/BlockLike
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:
https://github.com/ronilan/BlockLike
I suppose the biggest problem with `eval` is not getting uncontrolled user data into it.
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.
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.
I'm still holding off adding eval() to Snigl [0]. Not because of JIT clashes, since it is a straight interpreter; but because of the can of worms it opens. The thing is that I have to compile code in some kind of context, and depending on what the code says, it may or may not be safe to throw the compiled code away once done. Which means that I'd have to deal with a growing mountain of compiled code or complicate the design to support a feature that's mostly the wrong answer.
https://gitlab.com/sifoo/snigl
https://gitlab.com/sifoo/snigl
This is tangential, but I think that the way that the way Lisp does eval with its macros is incredibly elegant. There was a post on HN a few weeks ago [1] about doing symbolic derivatives in Lisp via quotations and eval, and while that was probably old news to most of the HN audience, it was downright magic to me, and the use of eval seemed appropriate.
[1] https://news.ycombinator.com/item?id=18343652
[1] https://news.ycombinator.com/item?id=18343652
Just my personal experience, but I used eval way more often when I didn't really know what I was doing. As I've learned Javascript, I've realized that in the vast majority of cases it's simply not needed.
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".
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".
Eval is a lot like goto in that way. In the vast majority of cases, there's a better way to do things -- but in a very, very small number of scenarios, it's the right tool for the job.
Can eval ever increase performance? I’m thinking that for some types of abstractions you can end up with highly nested, indirect calls, and if you can instead generate new code as needed, maybe it could speed things up? Are there any mainstream examples of this?
2 examples for optimizing for LuaJIT:
https://github.com/pygy/LuLPeg https://github.com/pygy/strung.lua
https://github.com/pygy/LuLPeg https://github.com/pygy/strung.lua
I think the Python falcon web server uses eval to produce direct python that executes routing code at run time. I don't have any reason to think it can't be beat, but it is relatively fast for something done in Python.
More generally, generating code is used for a lot of regex engines, though it may happen before runtime.
More generally, generating code is used for a lot of regex engines, though it may happen before runtime.
This looks like a compilation step. If you cannot have your language implementation generate some kind of optimized code statically, you can do it yourself dynamically.
A while back I used eval in a matrix multiplication routine, to make it accept matrices of arbitrary size and generate code to multiply them:
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.
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.
Eval has been evil since medireview times.
https://revealingerrors.com/medireview
https://revealingerrors.com/medireview
The answer to the question (consistent with all headline questions) is "no."
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.
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.
The thing is, it isn't just javascript eval; any eval is bad.
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.
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.
> any eval is bad
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.
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.
> The thing is, it isn't just javascript eval; any eval is bad.
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
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
> The thing is, it isn't just javascript eval; any eval is bad.
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.
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.
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 :)