Когда eval () в JavaScript не является злом? - PullRequest
242 голосов
/ 13 октября 2008

Я пишу некоторый код JavaScript для анализа введенных пользователем функций (для работы с таблицами). Проанализировав формулу, я может преобразовать ее в JavaScript и запустить eval() для получения результата.

Однако я всегда уклонялся от использования eval(), если я могу избежать этого, потому что это зло (и, правильно или неправильно, я всегда думал, что это еще более зло в JavaScript, потому что код, который нужно оценивать может быть изменен пользователем).

Итак, когда можно использовать его?

Ответы [ 24 ]

252 голосов
/ 13 октября 2008

Я хотел бы воспользоваться моментом, чтобы ответить на предпосылку вашего вопроса - этот eval () равен " evil ". Слово " зло ", используемое людьми на языке программирования, обычно означает "опасный", или, точнее, "способный причинить много вреда простой командой". Итак, когда можно использовать что-то опасное? Когда вы знаете, в чем опасность, и когда вы принимаете соответствующие меры предосторожности.

Кстати, давайте посмотрим на опасности при использовании eval (). Вероятно, есть много мелких скрытых опасностей, как и все остальное, но есть два больших риска - причина, по которой eval () считается злом - это производительность и внедрение кода.

  • Производительность - eval () запускает интерпретатор / компилятор. Если ваш код скомпилирован, то это большой успех, потому что вам нужно вызвать, возможно, тяжелый компилятор во время выполнения. Тем не менее, JavaScript по-прежнему в основном интерпретируемый язык, а это значит, что вызов eval () не сильно влияет на производительность в общем случае (но см. Мои конкретные замечания ниже).
  • Внедрение кода - eval () потенциально запускает строку кода с повышенными привилегиями. Например, программа, работающая как администратор / root, никогда не захочет eval () пользовательского ввода, потому что этот ввод может быть «rm -rf / etc / важный-файл» или хуже. Опять же, JavaScript в браузере не имеет этой проблемы, потому что программа все равно запускается под собственной учетной записью пользователя. Серверный JavaScript может иметь эту проблему.

В вашем конкретном случае. Насколько я понимаю, вы сами генерируете строки, поэтому, если вы будете осторожны, чтобы не генерировать строку типа "rm -rf что-то важное", нет риска внедрения кода (но, пожалуйста, помните, это очень, очень трудно для обеспечения этого в общем случае). Кроме того, если вы работаете в браузере, то, как мне кажется, внедрение кода представляет собой довольно незначительный риск.

Что касается производительности, вам придется сравнить ее с простотой кодирования. По моему мнению, если вы анализируете формулу, вы можете также вычислить результат во время анализа, а не запускать другой анализатор (тот, что внутри eval ()). Но это может быть проще для кода с использованием eval (), и снижение производительности, вероятно, будет незаметным. Похоже, что eval () в этом случае не более злой, чем любая другая функция, которая может сэкономить вам время.

71 голосов
/ 13 октября 2008

eval() не зло. Или, если это так, это зло так же, как отражение, файловый / сетевой ввод / вывод, многопоточность и IPC являются «злом» в других языках.

Если для вашей цели , eval() быстрее, чем ручная интерпретация, или делает ваш код проще или понятнее ... тогда вы должны его использовать. Если ни то, ни другое Все просто.

56 голосов
/ 13 октября 2008

Когда вы доверяете источнику.

В случае JSON более или менее сложно вмешаться в источник, потому что он исходит от веб-сервера, которым вы управляете. Пока сам JSON не содержит данных, загруженных пользователем, нет существенного недостатка в использовании eval.

Во всех других случаях я бы сделал все возможное, чтобы пользовательские данные соответствовали моим правилам перед передачей их в eval ().

24 голосов
/ 14 апреля 2010

Давайте получим реальных людей:

  1. В каждом крупном браузере теперь есть встроенная консоль, которую ваш потенциальный хакер может с избытком использовать для вызова любой функции с любым значением - почему они потрудились бы использовать оператор eval - даже если бы могли?

  2. Если для компиляции 2000 строк JavaScript требуется 0,2 секунды, какова будет моя производительность, если я получу четыре строки JSON?

Даже объяснение Крокфорда о «злом - это зло» слабо.

eval is Evil, функция eval является наиболее неправильно используемой функцией JavaScript. Избегайте этого

Как мог бы сам Крокфорд сказать: «Такое утверждение порождает иррациональный невроз. Не покупайте его».

Понимание eval и знание того, когда это может быть полезно, гораздо важнее. Например, eval - это разумный инструмент для оценки ответов сервера, сгенерированных вашим программным обеспечением.

Кстати: Prototype.js вызывает eval напрямую пять раз (в том числе в evalJSON () и evalResponse ()). jQuery использует его в parseJSON (через конструктор функций).

17 голосов
/ 13 октября 2008

Я склонен следовать совету Крокфорда для eval() и полностью его избегать. Даже способы, которые, кажется, требуют этого, не делают. Например, setTimeout() позволяет вам передавать функцию вместо eval.

setTimeout(function() {
  alert('hi');
}, 1000);

Даже если это доверенный источник, я им не пользуюсь, потому что код, возвращаемый JSON, может быть искажен, что в лучшем случае может сделать что-то нехорошее, а в худшем - показать что-то плохое.

4 голосов
/ 13 октября 2008

Я видел, как люди выступают за то, чтобы не использовать eval, потому что это зло , но я видел, как те же люди динамически используют Function и setTimeout, поэтому они используют eval под капотами : D

Кстати, если ваша песочница недостаточно уверена (например, если вы работаете на сайте, где разрешено внедрение кода), eval - это последняя из ваших проблем. Основное правило безопасности заключается в том, что ввод all является злом, но в случае JavaScript даже сам JavaScript может быть злом, потому что в JavaScript вы можете перезаписать любую функцию, а вы просто не можете убедитесь, что вы используете реальный, поэтому, если перед вами запускается вредоносный код, вы не можете доверять любой встроенной функции JavaScript: D

Теперь эпилог к ​​этому посту:

Если вам ДЕЙСТВИТЕЛЬНО это нужно (80% времени eval НЕ необходимо), и вы уверены в том, что делаете, просто используйте eval (или лучшую функцию ;)), замыкания и ООП покрывают 80/90% случаев, когда eval может быть заменен с использованием другой логики, остальное - динамически генерируемый код (например, если вы пишете интерпретатор), и, как вы уже сказали оценка JSON (здесь вы можете использовать безопасную оценку Крокфорда;))

4 голосов
/ 19 апреля 2013

Eval дополняет компиляцию, которая используется в шаблонах кода. Под шаблонизацией я подразумеваю, что вы пишете упрощенный генератор шаблонов, который генерирует полезный шаблонный код, который увеличивает скорость разработки.

Я написал фреймворк, в котором разработчики не используют EVAL, но они используют наш фреймворк, и в свою очередь этот фреймворк должен использовать EVAL для генерации шаблонов.

Производительность EVAL может быть увеличена с помощью следующего метода; вместо выполнения сценария вы должны вернуть функцию.

var a = eval("3 + 5");

Должно быть организовано как

var f = eval("(function(a,b) { return a + b; })");

var a = f(3,5);

Кэширование f, безусловно, улучшит скорость.

Также Chrome позволяет очень легко отлаживать такие функции.

Что касается безопасности, использование eval или нет, вряд ли будет иметь значение,

  1. Прежде всего, браузер вызывает весь сценарий в песочнице.
  2. Любой код, который является злом в EVAL, является злом в самом браузере. Злоумышленник или кто-либо другой может легко внедрить узел сценария в DOM и сделать что-нибудь, если он / она сможет что-либо проверить. Не использовать EVAL не будет иметь никакого значения.
  3. В основном это плохая защита на стороне сервера. Плохая проверка файлов cookie или плохая реализация ACL на сервере вызывают большинство атак.
  4. В нативном коде Java была недавняя уязвимость Java и т. Д. JavaScript был и предназначен для работы в песочнице, в то время как апплеты предназначены для работы вне песочницы с сертификатами и т. Д., Которые приводят к уязвимостям и многим другим.
  5. Написание кода для имитации браузера не составляет труда. Все, что вам нужно сделать, это сделать HTTP-запрос на сервер с вашей любимой строкой агента пользователя. Все инструменты тестирования в любом случае имитируют браузеры; если злоумышленник хочет причинить вам вред, EVAL является их последним средством. У них есть много других способов защиты вашей серверной стороны.
  6. Браузер DOM не имеет доступа к файлам и не имеет имени пользователя. На самом деле ничто на компьютере, к которому eval может предоставить доступ.

Если ваша серверная безопасность достаточно надежна, чтобы кто-то мог атаковать откуда угодно, вам не стоит беспокоиться о EVAL. Как я уже говорил, если EVAL не существует, у злоумышленников есть много инструментов для взлома вашего сервера независимо от возможностей EVAL вашего браузера.

Eval хорош только для генерации некоторых шаблонов для выполнения сложной обработки строк на основе чего-то, что не используется заранее. Например, я предпочту

"FirstName + ' ' + LastName"

В отличие от

"LastName + ' ' + FirstName"

Как мое отображаемое имя, которое может быть получено из базы данных и не имеет жесткого кода.

4 голосов
/ 23 июля 2013

При отладке в Chrome (v28.0.1500.72) я обнаружил, что переменные не связаны с замыканиями, если они не используются во вложенной функции, которая создает замыкание. Думаю, это оптимизация движка JavaScript.

НО : когда eval() используется внутри функции, вызывающей замыкание, ALL переменные внешних функций привязываются к замыканию, даже если они не используются в все. Если у кого-то есть время проверить, могут ли быть вызваны утечки памяти, оставьте мне комментарий ниже.

Вот мой тестовый код:

(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();
})();

Здесь я хотел бы отметить, что eval () не обязательно должен ссылаться на встроенную функцию eval(). Все зависит от названия функции . Таким образом, при вызове нативного eval() с псевдонимом (скажем, var noval = eval;, а затем во внутренней функции noval(expression);) вычисление expression может завершиться неудачей, когда оно ссылается на переменные, которые должны быть частью замыкания, но на самом деле нет.

3 голосов
/ 11 февраля 2009

Microsoft объясняет, почему eval () работает медленно в их браузере в блоге IE, Рекомендации по производительности IE + JavaScript, часть 2. Неэффективность кода JavaScript .

2 голосов
/ 13 октября 2008

Единственный случай, когда вы должны использовать eval (), это когда вам нужно запускать динамический JS на лету. Я говорю о JS, который вы асинхронно скачиваете с сервера ...

... И 9 раз из 10 вы можете легко избежать этого путем рефакторинга.

...