J'écris du code JavaScript pour analyser les fonctions entrées par l'utilisateur (pour une fonctionnalité semblable à celle d'un tableur). Après avoir analysé la formule I pourrait le convertir en JavaScript et exécuter eval()
sur celui-ci pour obtenir le résultat.
Cependant, j'ai toujours craint d'utiliser eval()
si je peux l'éviter parce que c'est mauvais (et, à tort ou à raison, j'ai toujours pensé que c'était encore plus mauvais en JavaScript, parce que le code à être évalué peut être modifié par l'utilisateur).
Alors, quand est-il possible de l'utiliser?
Je voudrais prendre un moment pour répondre à la prémisse de votre question - eval () est "evil". Le mot "evil", tel qu'utilisé par les langeurs de programmation, signifie généralement "dangereux", ou plus précisément "capable de causer beaucoup de tort avec une simple commande". Alors, quand est-il acceptable d'utiliser quelque chose de dangereux? Quand vous savez quel est le danger et quand vous prenez les précautions appropriées.
Regardons les dangers de l’utilisation de eval (). Comme tout le reste, il existe probablement de nombreux petits dangers cachés, mais les deux grands risques - la raison pour laquelle eval () est considérée comme diabolique - sont les performances et l’injection de code.
Sur votre cas spécifique. D'après ce que j'ai compris, vous générez vous-même les chaînes. En supposant que vous prenez bien soin de ne pas générer de chaîne telle que "rm -rf quelque chose d'important", il n'y a aucun risque d'injection de code (mais rappelez-vous, c'est - très très difficile pour assurer cela dans le cas général). De plus, si vous utilisez le navigateur, l’injection de code est un risque plutôt mineur, je crois.
En ce qui concerne les performances, vous devrez peser cela par rapport à la facilité de codage. À mon avis, si vous analysez la formule, vous pouvez également calculer le résultat au cours de l'analyse plutôt que d'exécuter un autre analyseur (celui situé à l'intérieur de eval ()). Mais il peut être plus facile de coder en utilisant eval (), et l'impact sur les performances sera probablement imperceptible. Dans ce cas, il semble qu'éval () ne soit pas plus diabolique que n'importe quelle autre fonction susceptible de vous faire gagner du temps.
eval()
n'est pas mauvais. Ou bien, si c'est le cas, c'est mal de la même manière que la réflexion, les E/S de fichiers/réseau, les threads et IPC sont "diaboliques" dans d'autres langues.
Si, pour votre propos, eval()
est plus rapide que l'interprétation manuelle, ou rend votre code plus simple, ou plus clair ... alors vous devriez l'utiliser. Si ni l'un ni l'autre, alors vous ne devriez pas. Aussi simple que cela.
Quand vous faites confiance à la source.
Dans le cas de JSON, il est plus ou moins difficile de modifier le code source, car il provient d'un serveur Web que vous contrôlez. Tant que le JSON lui-même ne contient aucune donnée téléchargée par un utilisateur, l'utilisation d'eval ne présente pas d'inconvénient majeur.
Dans tous les autres cas, je ferais beaucoup pour m'assurer que les données fournies par l'utilisateur sont conformes à mes règles avant de les transmettre à eval ().
Obtenons de vrais gens:
Tous les principaux navigateurs ont maintenant une console intégrée que votre pirate potentiel peut utiliser en abondance pour invoquer n'importe quelle fonction avec n'importe quelle valeur. Pourquoi s'embarrasseraient-ils d'utiliser une déclaration eval, même s'ils le pouvaient?
S'il faut 0,2 seconde pour compiler 2 000 lignes de JavaScript, quelle est la dégradation de mes performances si j'évalue quatre lignes de JSON?
Même l'explication de Crockford pour "eval is evil" est faible.
eval is Evil, la fonction eval est la fonctionnalité la plus mal utilisée de JavaScript. L'éviter
Comme pourrait le dire Crockford lui-même: "Ce genre d'affirmation tend à générer une névrose irrationnelle. Ne l'achetez pas."
Comprendre eval et savoir quand cela pourrait être utile est bien plus important. Par exemple, eval est un outil utile pour évaluer les réponses du serveur générées par votre logiciel.
BTW: Prototype.js appelle directement eval cinq fois (y compris dans evalJSON () et evalResponse ()). jQuery l'utilise dans parseJSON (via le constructeur de fonctions).
J'ai tendance à suivre conseil de Crockford pour eval()
, et à l'éviter complètement. Même les moyens qui semblent l'exiger ne le font pas. Par exemple, la setTimeout()
vous permet de transmettre une fonction plutôt que eval.
setTimeout(function() {
alert('hi');
}, 1000);
Même s'il s'agit d'une source digne de confiance , je ne l'utilise pas, car le code renvoyé par JSON pourrait être tronqué, ce qui pourrait, au mieux, faire quelque chose d'étrange, au pire, exposer quelque chose de mauvais.
Lors du débogage dans Chrome (v28.0.1500.72), j'ai constaté que les variables ne sont pas liées aux fermetures si elles ne sont pas utilisées dans une fonction imbriquée qui produit la fermeture. Je suppose que c'est une optimisation du moteur JavaScript.
MAIS : quand eval()
est utilisé dans une fonction qui provoque une fermeture, TOUT les variables des fonctions externes sont liées à la fermeture, même si elles ne sont pas utilisées du tout. Si quelqu'un a le temps de vérifier si des fuites de mémoire peuvent en résulter, laissez-moi un commentaire ci-dessous.
Voici mon code de test:
(function () {
var eval = function (arg) {
};
function evalTest() {
var used = "used";
var unused = "not used";
(function () {
used.toString(); // Variable "unused" is visible in debugger
eval("1");
})();
}
evalTest();
})();
(function () {
var eval = function (arg) {
};
function evalTest() {
var used = "used";
var unused = "not used";
(function () {
used.toString(); // Variable "unused" is NOT visible in debugger
var noval = eval;
noval("1");
})();
}
evalTest();
})();
(function () {
var noval = function (arg) {
};
function evalTest() {
var used = "used";
var unused = "not used";
(function () {
used.toString(); // Variable "unused" is NOT visible in debugger
noval("1");
})();
}
evalTest();
})();
Ce que j'aime souligner, c’est que eval () ne doit pas nécessairement faire référence à la fonction native eval()
. Tout dépend du nom de la fonction. Ainsi, lors de l'appel de la eval()
native avec un nom d'alias (disons var noval = eval;
Puis dans une fonction interne noval(expression);
), l'évaluation de expression
peut échouer. quand il fait référence à des variables qui devraient faire partie de la fermeture, mais ne le sont pas.
J'ai vu des gens préconiser de ne pas utiliser eval, parce que c'est diabolique , mais j'ai vu les mêmes personnes utiliser Function et setTimeout de manière dynamique; elles utilisent donc eval sous les capots : D
BTW, si votre sandbox n’est pas assez sûr (par exemple, si vous travaillez sur un site qui autorise l’injection de code), eval est le dernier de vos problèmes. La règle de base de la sécurité est que toutes les entrées sont mauvaises, mais dans le cas de JavaScript , même JavaScript lui-même pourrait être diabolique, car en JavaScript, vous pouvez écraser n'importe quelle fonction et vous ne pouvez pas être sûr de l'utiliser, alors si un code malveillant commence avant vous, vous ne pouvez faire confiance à aucune Fonction intégrée JavaScript: D
Maintenant, l'épilogue de ce post est:
Si vous VRAIMENT en avez besoin (80% du temps d'évaluation est PAS nécessaire) et vous êtes sûr de ce que vous faites, utilisez simplement eval (ou une meilleure fonction;)), les fermetures et OOP couvrez les 80/90% des cas où eval peut être remplacé en utilisant un autre type de logique, le reste est du code généré dynamiquement (par exemple, si vous écrivez un interprète) et, comme vous l'avez déjà dit, évaluer JSON (ici, vous pouvez utiliser l'évaluation de Crockford safe;))
Eval est complémentaire à la compilation utilisée dans la modélisation du code. Par modèles, je veux dire que vous écrivez un générateur de modèles simplifié qui génère un code de modèle utile qui augmente la vitesse de développement.
J'ai écrit un cadre dans lequel les développeurs n'utilisent pas EVAL, mais ils utilisent notre cadre et ce dernier doit utiliser EVAL pour générer des modèles.
Les performances d’EVAL peuvent être améliorées à l’aide de la méthode suivante; au lieu d'exécuter le script, vous devez renvoyer une fonction.
var a = eval("3 + 5");
Il devrait être organisé comme
var f = eval("(function(a,b) { return a + b; })");
var a = f(3,5);
Caching f va certainement améliorer la vitesse.
Aussi Chrome permet de déboguer très facilement de telles fonctions.
En ce qui concerne la sécurité, utiliser eval ou non ne fera pratiquement aucune différence,
Si votre sécurité côté serveur est suffisamment solide pour permettre à quiconque d'attaquer de n'importe où, vous ne devriez pas vous inquiéter d'EVAL. Comme je l'ai dit, si EVAL n'existait pas, les attaquants disposent de nombreux outils pour pirater votre serveur, quel que soit le navigateur utilisé. Capacité EVAL.
Eval n'est utile que pour générer des modèles permettant d'effectuer un traitement de chaîne complexe en fonction de quelque chose qui n'est pas utilisé à l'avance. Par exemple, je préférerais
"FirstName + ' ' + LastName"
Par opposition à
"LastName + ' ' + FirstName"
Comme mon nom d'affichage, qui peut provenir d'une base de données et qui n'est pas codé en dur.
Microsoft explique pourquoi eval () est lent dans son navigateur sur le IE Blog, IE + Recommandations en matière de performances JavaScript, partie 2: inefficacité du code JavaScript.
La seule instance dans laquelle vous devriez utiliser eval () est celle où vous devez exécuter JS dynamique à la volée. Je parle de JS que vous téléchargez de manière asynchrone à partir du serveur ...
... Et 9 fois sur 10, vous pourriez facilement éviter cela en procédant à une refactorisation.
Côté serveur, eval est utile lorsqu'il s'agit de scripts externes tels que SQL ou influxdb ou mongo. La validation personnalisée au moment de l'exécution peut être effectuée sans redéployer vos services.
Par exemple, un service de réalisation avec les métadonnées suivantes
{
"568ff113-abcd-f123-84c5-871fe2007cf0": {
"msg_enum": "quest/registration",
"timely": "all_times",
"scope": [
"quest/daily-active"
],
"query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" LIMIT 1`",
"validator": "valid > 0",
"reward_external": "ewallet",
"reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/registration:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/registration\"}`"
},
"efdfb506-1234-abcd-9d4a-7d624c564332": {
"msg_enum": "quest/daily-active",
"timely": "daily",
"scope": [
"quest/daily-active"
],
"query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" WHERE time >= '${today}' ${ENV.DAILY_OFFSET} LIMIT 1`",
"validator": "valid > 0",
"reward_external": "ewallet",
"reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/daily-active:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/daily-active\"}`"
}
}
Ce qui permet alors,
Injection directe d'objet/de valeurs par une chaîne littérale dans un json, utile pour la modélisation de textes
Peut être utilisé comme comparateur, disons que nous établissons des règles pour valider une quête ou des événements dans le système de gestion de contenu
Con de ceci:
Peut-être des erreurs dans le code et casser des choses dans le service, si pas entièrement testé.
Si un pirate informatique peut écrire un script sur votre système, vous êtes plutôt mal foutu.
Une façon de valider votre script consiste à conserver le hachage de vos scripts dans un endroit sûr, afin de pouvoir les vérifier avant de les exécuter.
Si vous avez créé ou désinfecté le code vous eval
, il n'est jamais mal.
eval
est mal s'il est exécuté sur le serveur à l'aide d'une entrée soumise par un client qui était non créé par le développeur ou qui était non assaini par le développeur.
eval
est pas mal si exécuté sur le client, même si vous utilisez une entrée non authentifiée conçue par le client.
Évidemment, vous devriez toujours nettoyer l'entrée, de manière à avoir un certain contrôle sur ce que votre code consomme.
Le client peut exécuter n'importe quel code arbitraire qu'il souhaite, même si le développeur ne l'a pas codé. Ceci est vrai non seulement pour ce que est évalué, mais l'appel à eval
lui-même .
Je pense que tous les cas où eval serait justifié seraient rares. Vous êtes plus susceptible de l'utiliser en pensant qu'il est justifié que de l'utiliser lorsque c'est réellement justifié.
Les problèmes de sécurité sont les plus connus. Mais sachez également que JavaScript utilise la compilation JIT et que cela fonctionne très mal avec eval. Eval est un peu comme une boîte noire pour le compilateur, et JavaScript doit pouvoir prédire le code à l'avance (dans une certaine mesure) afin d'appliquer correctement et en toute sécurité des optimisations de performances et une portée. Dans certains cas, l’impact sur les performances peut même affecter un autre code en dehors de eval.
Si vous voulez en savoir plus: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval
eval
est rarement le bon choix. Bien qu'il puisse y avoir de nombreux cas où vous pouvez accomplir ce que vous devez faire en concaténant un script et en l'exécutant à la volée, vous disposez généralement de techniques beaucoup plus puissantes et maintenables: la notation associative-array (obj["prop"]
est le même que obj.prop
), fermetures, techniques orientées objet, techniques fonctionnelles - utilisez-les plutôt.
Vous pouvez l'utiliser si vous avez le contrôle total sur le code transmis à la fonction eval
.
Quand eval () de JavaScript n'est-il pas mauvais?
J'essaie toujours de déconseille d'utiliser eval . Presque toujours, une solution plus propre et maintenable est disponible. Eval n'est pas nécessaire même pour l'analyse JSON . Eval ajoute à la maintenance . Non sans raison, il est mal vu par des maîtres comme Douglas Crockford.
Mais j'ai trouvé un exemple où il devrait être utilisé:
Par exemple, j'ai une fonction qui construit un général google.maps.ImageMapType
objet pour moi, mais je dois lui dire la recette, comment doit-il construire l'URL de la tuile à partir des paramètres zoom
et coord
:
my_func({
name: "OSM",
tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
...
});
function my_func(opts)
{
return new google.maps.ImageMapType({
getTileUrl: function (coord, zoom) {
var b = zoom;
var a = coord;
return eval(opts.tileURLexpr);
},
....
});
}
Si c'est vraiment nécessaire, eval n'est pas mauvais. Mais 99,9% des utilisations de eval qui me tombent dessus sont non nécessaires (sans les éléments setTimeout).
Pour moi, le mal n’est pas une performance ni même un problème de sécurité (indirectement, c’est les deux). Toutes ces utilisations inutiles d’eval contribuent à un enfer de maintenance. Les outils de refactoring sont supprimés. La recherche de code est difficile. Les effets imprévus de ces évaluations sont légion.
Génération de code. J'ai récemment écrit une bibliothèque appelée Hyperbars qui comble le fossé entre virtual-dom et guidon . Pour ce faire, il analyse un modèle de guidon et le convertit en hyperscript . L’hyperscript est d'abord généré sous forme de chaîne et, avant de le renvoyer, eval()
pour le transformer en code exécutable. J'ai trouvé eval()
dans cette situation particulière l'exact opposé du mal.
Fondamentalement de
<div>
{{#each names}}
<span>{{this}}</span>
{{/each}}
</div>
Pour ça
(function (state) {
var Runtime = Hyperbars.Runtime;
var context = state;
return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
return [h('span', {}, [options['@index'], context])]
})])
}.bind({}))
Les performances de eval()
ne sont pas un problème dans une situation comme celle-ci, car il vous suffit d'interpréter la chaîne générée une fois, puis de réutiliser plusieurs fois la sortie de l'exécutable.
Vous pouvez voir comment la génération de code a été réalisée si vous êtes curieux ici .
Mon exemple d'utilisation de eval
: import.
Comment c'est habituellement fait.
var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring
Mais avec l’aide de eval
et une petite fonction d’aide, cela donne une bien meilleure apparence:
var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));
importable
pourrait ressembler à (cette version ne supporte pas l'importation de membres concrets).
function importable(path) {
var name;
var pkg = eval(path);
var result = '\n';
for (name in pkg) {
result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
}
for (name in pkg) {
result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
}
return result;
}
Seulement pendant les tests, si possible. Notez également que eval () est beaucoup plus lent que d’autres évaluateurs spécialisés JSON, etc.
Il n'y a aucune raison de ne pas utiliser eval () tant que vous pouvez être sûr que la source du code provient de vous ou de l'utilisateur réel. Même s'il peut manipuler ce qui est envoyé dans la fonction eval (), ce n'est pas un problème de sécurité, car il est capable de manipuler le code source du site Web et peut donc modifier le code JavaScript lui-même.
Alors ... quand ne pas utiliser eval ()? Eval () ne doit être utilisé que lorsqu'un tiers peut le modifier. C'est comme intercepter la connexion entre le client et votre serveur (mais si cela pose un problème, utilisez HTTPS). Vous ne devriez pas utiliser eval () pour analyser le code écrit par d'autres personnes, comme dans un forum.
En ce qui concerne le script client, je pense que la question de la sécurité est une question discutable. Tout ce qui est chargé dans le navigateur est sujet à manipulation et doit être traité comme tel. Il n'y a aucun risque à utiliser une instruction eval () lorsqu'il existe des moyens beaucoup plus simples d'exécuter du code JavaScript et/ou de manipuler des objets dans le DOM, tels que la barre d'URL de votre navigateur.
javascript:alert("hello");
Si quelqu'un veut manipuler son DOM, je dis balancer. La sécurité pour empêcher tout type d’attaque devrait toujours être de la responsabilité de l’application serveur, point final.
D'un point de vue pragmatique, il n'y a aucun avantage à utiliser eval () dans une situation où il est possible de faire autre chose. Cependant, il existe des cas spécifiques dans lesquels une évaluation DEVRAIT être utilisée. Dans ce cas, cela peut être fait sans risque de faire sauter la page.
<html>
<body>
<textarea id="output"></textarea><br/>
<input type="text" id="input" />
<button id="button" onclick="execute()">eval</button>
<script type="text/javascript">
var execute = function(){
var inputEl = document.getElementById('input');
var toEval = inputEl.value;
var outputEl = document.getElementById('output');
var output = "";
try {
output = eval(toEval);
}
catch(err){
for(var key in err){
output += key + ": " + err[key] + "\r\n";
}
}
outputEl.value = output;
}
</script>
<body>
</html>
Eval est utile pour la génération de code lorsque vous n'avez pas de macros.
Pour un exemple (stupide), si vous écrivez un compilateur Brainfuck , vous voudrez probablement construire une fonction qui exécute la séquence d'instructions sous la forme d'une chaîne, et l'évaluez pour renvoyer une fonction.
Ma conviction est que eval est une fonction très puissante pour les applications Web côté client et sûre ... Aussi sûre que JavaScript, qui ne le sont pas. :-) Les problèmes de sécurité sont essentiellement liés au serveur car avec un outil comme Firebug, vous pouvez attaquer n’importe quelle application JavaScript.