6

As a form of input validation I need to coerce a string like '9>6' to evaluate to a boolean value.

Apart from evaluating the string I can't seem to find a workaround .

I've always heard about the evilness of eval (especially since I'm validating a form input), about the fact that it could evaluate any script and performance issues.

but....

Are there any alternative in my case (dealing with relational operators)?

var arr = ['<9', '>2'];

var check = function (a) {

    return arr.every(function (x) {
            var string = '';

            string += a + x;

            try {
                return eval(string);
            } catch (e) {
                return false;
            }
        });
    };

console.log(check('3'))
maioman
  • 18,154
  • 4
  • 36
  • 42
  • 2
    your could evaluate an interpreter pattern http://www.dofactory.com/javascript/interpreter-design-pattern – ale Sep 04 '15 at 11:47
  • or how about using a webworker and using eval there, and evaluate it out of context of the rest of your application – atmd Sep 04 '15 at 11:51
  • @atmd this issue came up on a local script which received the values through form inputs, it actually works quite well (no noticeable performance issues). I couldn't find another simple solutions and I don't have any practical reason to really search an alternative, but since eval has such a bad reputation . . . – maioman Sep 04 '15 at 11:59
  • 1
    @atmd: What would using a web worker help here, except for added complexity? – Bergi Sep 04 '15 at 11:59
  • 1
    Did you even try looking for your answer before wasting our time? Close enough to be considered duplicates: [Why is using the JavaScript eval function a bad idea?](http://stackoverflow.com/questions/86513), [When is JavaScript's eval() not evil?](http://stackoverflow.com/questions/197769), [Why exactly is eval evil?](http://stackoverflow.com/questions/2571401), [JSLint “eval is evil.” alternatives](http://stackoverflow.com/questions/13147289). – Mogsdad Sep 04 '15 at 12:26
  • 2
    possible duplicate of [JSLint "eval is evil." alternatives](http://stackoverflow.com/questions/13147289/jslint-eval-is-evil-alternatives) – Mogsdad Sep 04 '15 at 12:27

3 Answers3

3

I wouldn't say eval is inherently evil, I only say that it has its advantages and disadvantages. In my practice I experienced very few cases where its advantages were numerous.

The two main problems with the eval:

  1. It needs runtime interpretation, i.e. string processing and interpreting from the javascript engine. It is slow. Normal javascript runs in a tokenized form.
  2. If you use eval, you also start a game where you want to block inserting any harmful code, without decreseased functionality, and the hackers trying to find a way through your blocks. And your only possibility to know you didn't lose that your site weren't cracked until now. You don't have any possibility to know that you won.

But: eval runs mainly on the browser side, or processes user input data (created by the same user), or server data which was generated by your server-side.

The solution in your case is obvious: use hard OO things, for example functors.

function comparator(lighter, num) {
  return function(x) {
    return lighter ? (x<num) : (x>num);
  }
}

var arr = [ comparator(true, '9'), comparator(false, '2') ];

var check = function(a) {
  return arr.every(function(comp) { return comp(a); });
}

console.log(check('3'));

On the first spot it is much more complex as your version, but it is only because you are accustomed to eval and aren't to tricky functor solutions. Actually, the complexity of the solutions are imho similar.

peterh
  • 11,875
  • 18
  • 85
  • 108
  • Did you just call functors "hard OO"? o_o – Bergi Sep 06 '15 at 21:16
  • I don't think #2 is very accurate. If you don't let users input the code, it doesn't really make it easier for the hackers. And that game started long times ago, it is played with or without usage of `eval`… – Bergi Sep 06 '15 at 21:18
  • @Bergi Yes, this is the problem - if you restrict the code, it will be disfunctional. But the attackers aren't bound this limit, they can do everything, you can do only which doesn't harm the function. – peterh Sep 07 '15 at 01:36
2

I can see three things to improve:

  • Instead of stringifying the a value and concatenating it to your expression, you should just refer to the a variable directly so that you can compare to the true form of the value. Otherwise you might get odd behaviour.

    return eval("a " + x);
    
  • Since you are probably using check multiple times, you should not call eval every time (or even multiple times per call). You can compose the strings into one large && expression, and even better you can compile the conditions to single functions beforehand so that you don't have to call eval from within check at all.

  • You should consider using the Function constructor instead of eval.

With those, your code might look like this:

var arr = ['<9', '>2'];

var check = new Function("a", "return "+arr.map(function(x) {
    return "a"+x;
}).join(" && ")+";");
console.log(check.toString()); // function anonymous(a) { return a<9 && a>2; }
console.log(check('3'));

or this:

var arr = ['<9', '>2'];
var fns = arr.map(function(x) {
    return new Function("a", "return a "+x+";");
});
function check(a) {
    return fns.every(function(f) {
        return f(a);
    });
}
console.log(check('3'));

Of course I am expecting that arr is not controlled by the user (or worse, another than the current user); if you expect your users to input those conditions you should make sure to whitelist them explicitly, or maybe use a more sophisticated expression grammar/parser/evaluator right away.
Unless that is the case, eval (or for that matter, Function) is not exactly evil, it just does shorten (and possible simplify) your code. You could've written out full-blown functions (like Nit and peterh suggested) just as well.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Function constructor also interprets strings in runtime, thus it also has all of the eval's disadvantages. – peterh Sep 04 '15 at 13:08
  • @peterh: The `Function` constructor avoids the problems with scope that `eval` has. Of course it does compile strings at runtime, but that's not necessarily a bad thing, especially if only done once like I suggested in my answer. – Bergi Sep 04 '15 at 13:22
  • @peterh: See also the questions to which Mogsdad linked in the comments. Basically, there only stays [disadvantage #2](http://stackoverflow.com/a/86580/1048572) - and [a few advantages](http://stackoverflow.com/a/197823/1048572). – Bergi Sep 04 '15 at 13:27
  • Well I admit it is better than eval. – peterh Sep 07 '15 at 01:35
  • at the moment the input which I run eval on is set to`number`; do you know if it's possible to trick `input type='number'` into accepting other values (and so become potentially vulnerable )? – maioman Sep 07 '15 at 08:59
  • @maioman: If you take the values from an input from the user who executes this things in his browser it's not more vulnerable than anything else. `` should ensure that he doesn't get any syntax errors, yes. Regardless, if you take the numbers from an input and not from a fixed (programmer-defined) string, you should not concatenate them to a `< 9` string anyway, but pass them as parameters to a function. See [here](http://stackoverflow.com/a/11088753/1048572) or [there](http://stackoverflow.com/a/12961206/1048572). – Bergi Sep 07 '15 at 13:24
2

One option would be to define helper functions, but how useful this approach would be depends largely on what you want to achieve in the larger picture.

function geq(n) {
    return function(m) {
        return m >= n;
    }
}

function leq(n) {
    return function(m) {
        return m <= n;
    }
}

var arr = [leq(9), geq(2)];

function check(n) {
    arr.forEach(function(checkAgainst) {
        console.log(checkAgainst(n));
    });
}
Etheryte
  • 24,589
  • 11
  • 71
  • 116
  • 2
    Actually, this is probably the simplest and safest solution. You can use it for arbitrarily complex validations, there's no need to do any explicit evals etc. It's even simpler in ES6. – Luaan Sep 04 '15 at 12:03
  • so far this seems the simplest solution; @Luaan could you write in ES6 syntax ? – maioman Sep 04 '15 at 12:14
  • 1
    @maioman Using ES6, the helpers are simply abbreviated, a-la `var geq = (n) => { return (m) => m >= n; }`, but the gist of the code is the same. – Etheryte Sep 04 '15 at 12:22
  • 2
    @maioman And of course, in addition, you can inline them - `var arr = [(x) => x < 9; (x) => x > 3];`. In some cases, you want explicit helper methods, but in other cases, simply writing out exactly the function you want inline is more readable and useful. Partial application can also be useful - in a way, that's what Nit does in his sample - he returns a function based on an argument of another function, but it doesn't really show with something as simple as `m < n`. – Luaan Sep 04 '15 at 12:26