JSLint "eval is evil." alternatives

11,920

Solution 1

I wouldn't worry about it since you are only passing these function strings from the server to the client, and are thus in control of what will be evaluated.

On the other hand, if you were going the other direction and doing the evals of client-passed code on the server, that would be an entirely different story...

Update:

As disabling the validation option in your comment may cause you to miss future errors, I would instead suggest passing the function name rather than the entire function and have the function library mirrored on the server and client. Thus, to call the function, you'd use the following code:

var policyFunction = YourLibraryName[this.policies[j].policyFunctionName];
var policyArguments = this.policies[j].policyArguments;

policyFunction.apply(this, policyArguments); 

Update 2:

I was able to validate the following code with JSLint successfully, which essentially allows you to "turn off" validation for the vast minority of cases where eval is appropriate. At the same time, JSLint still validates normal eval calls, and all uses of this method should throw up flags for future developers to avoid using it/refactor it out where possible/as time allows.

var EVAL_IS_BAD__AVOID_THIS = eval;
EVAL_IS_BAD__AVOID_THIS(<yourString>);

Solution 2

Dont encode a function as a string in JSON. JSON is for content, which you are confounding with behavior.

Instead, I suppose you could return JS files instead, which allow real functions:

 { name : "phoneNumber",
    policies : [ 
        { policyFunction : function() {
              whateverYouNeed('here');
          }
        }
      ]
  }

But while that solves the technical issue, it's still not a great idea.


The real solution here is to move your logic out of your content entirely. Import a JS file full of little validation functions and call them as needed based on a dataType property in your JSON or something. If this functions are as small and portable as you say, this should be trivial to accomplish.

Getting your data all tangled up with your code usually leads to pain. You should statically include your JS, then dynamically request/import/query for your JSON data to run through your statically included code.

Solution 3

I would avoid using eval in all situations. There's no reason you can't code around it. Instead of sending code to the client, just keep it hosted on the server in one contained script file.

If that's not doable, you can also have a dynamically generated javascript file then pass in the necessary parameters via the response, and then dynamically load the script on the client side. There's really no reason to use eval.

Hope that helps.

Solution 4

You can use

setInterval("code to be evaluated", 0);

Internally, if you pass setInterval a string it performs a function similar to eval().

However, I wouldn't worry about it. If you KNOW eval() is evil, and take appropriate precautions, it's not really a problem. Eval is similar to GoTo; you just have to be careful and aware of what you're doing to use them properly.

Solution 5

DRY is definitely something I agree with, however there is a point where copy+pasting is more efficient and easy to maintain than referencing the same piece of code.

The code you're saving yourself from writing seems to be equivalent to a clean interface, and simple boiler plate. If the same code is being used on both the server and the client, you could simply pass around the common pieces of the function, rather than the whole function.

Payload:
{
    "name": "phoneNumber",
    "type": "regexCheck",
    "checkData": "/^\\+?([0-9\\- \\(\\)])*$/"
}
if(payload.type === "regexCheck"){
    const result = validPhoneFormat(fullObject, value, payload.checkData)
}

function validPhoneFormat(fullObject, value, regexPattern) {

    if (value && value.length && !regexPattern.test(value))
        return [ {"policyRequirement": "VALID_PHONE_FORMAT"}];
    else
        return [];
}

This would give you the ability to update the regex from a single location. If the interface changes it does need to be updated in 2 places, but I wouldn't consider that a bad thing. If the client is running code, why hide the structure?

If you really, really want to keep both the object structure and the patterns in one place - extract it to a single API. Have a "ValidatePhoneViaRegex" api endpoint which is called by all places you'd be passing this serialized function to.

If all of this seems like too much effort, set jslint to ignore your piece of code:

"In JSHint 1.0.0 and above you have the ability to ignore any warning with a special option syntax. The identifier of this warning is W061. This means you can tell JSHint to not issue this warning with the /*jshint -W061 */ directive.

In ESLint the rule that generates this warning is named no-eval. You can disable it by setting it to 0, or enable it by setting it to 1."

https://github.com/jamesallardice/jslint-error-explanations/blob/master/message-articles/eval.md

I would prefer to see copy+pasted code, a common api, or receiving parameters and copy+pasted boiler plate than magical functions passed in from the server to be executed.

What happens if you get a cross-browser compatibility error with one of these shared functions?

Share:
11,920
Jake Feasel
Author by

Jake Feasel

Web Developer, Identity and Access Management Guy at ForgeRock Created SQL Fiddle Made a couple OAuth 2 / OIDC libraries for single page apps: appAuthHelper oidcSessionCheck twitter - @jakefeasel Don't tell me I can't be a bear!

Updated on June 24, 2022

Comments

  • Jake Feasel
    Jake Feasel almost 2 years

    I am have some JavaScript functions that run on both the client (browser) and the server (within a Java Rhino context). These are small functions - basically little validators that are well defined and don't rely upon globals or closures - self-contained and portable.

    Here's an example:

    function validPhoneFormat(fullObject, value, params, property) {
        var phonePattern = /^\+?([0-9\- \(\)])*$/;
        if (value && value.length && !phonePattern.test(value))
            return [ {"policyRequirement": "VALID_PHONE_FORMAT"}];
        else
            return [];
    }
    

    To keep things DRY, my server code gets a handle on each of these functions and calls toString() on them, returning them to the browser as part of a JSON object. Something like this:

          { "name" : "phoneNumber",
            "policies" : [ 
                { "policyFunction" : "\nfunction validPhoneFormat(fullObject, value, params, property) {\n    var phonePattern = /^\\+?([0-9\\- \\(\\)])*$/;\n    if (value && value.length && !phonePattern.test(value)) {\n        return [{\"policyRequirement\":\"VALID_PHONE_FORMAT\"}];\n    } else {\n        return [];\n    }\n}\n"
                }
              ]
          }
    

    My browser JS code then takes this response and creates an instance of this function in that context, like so:

    eval("var policyFunction = " + this.policies[j].policyFunction);
    
    policyFailures = policyFunction.call(this, form2js(this.input.closest("form")[0]), this.input.val(), params, this.property.name));
    

    This all works very well. However, I then run this code through JSLint, and I get back this message:

    [ERROR] ValidatorsManager.js:142:37:eval is evil.

    I appreciate that often, eval can be dangerous. However, I have no idea how else I could implement such a mechanism without using it. Is there any way I can do this and also pass through the JSLint validator?