Eval is evil: Consideraciones

Índice

Evil or not evil

Crockford es cosa seria

Antes de empezar, es bueno hacer notar que las afirmaciones tan intransigentes como ‘___ is evil’ no deberían darse sin un contexto específico en el cual se pueda justificar dicha afirmación (si es que existe el contexto en cuestión). Siendo tal que lo que en una situación puntual es útil y hasta aconsejable, no lo sea en otra.

Por eso mismo, no es aconsejable dejarse llevar por afirmaciones dogmaticas realizadas al respecto, aún si vienen de una eminencia del tema:

Douglas Crockford:

“eval is Evil: The eval function is the most misused feature of JavaScript. Avoid it”

John Resig:

“Code evaluation is simultaneously the most advanced and most frequently misused feature of JavaScript. Understanding the situations in which it can and should be used (along with the best techniques for using it) can give you a definite advantage when creating advanced applications.

Siendos un poco más justos con la línea de pensamiento del señor Crockford, hay que admitir que es excesivo el uso de la sentencia eval por parte de los programadores novatos, los cuales utilizan dicha herramienta para paliar su falta de conocimiento/interés en soluciones más propicias.

Douglas Crockford:

“… it turns out that if you have absolutely no idea what you’re doing in the language you can still generally make things work.”


Presentaciones

Este “maligno” arte de evaluar código se presenta en diversas formas dentro de JavaScript, de la cual la más obvia y utilizada es la sentencia eval.

eval(string)

El uso eval es la forma más común de evalúar código en JavaScript, su sintaxis es la siguiente:

// Devuelve el valor de la expresion basica
var myVar = eval('1');

console.log(myVar); //1

// Genera la asignacion dinamicamente
console.log(eval('myVar *= 2')); //2

En la documentación de Mozilla (MDN) podemos observar la definición bastante general y acertada:

eval is a top-level function and is not associated with any object.

The argument of the eval function is a string. If the string represents an expression, eval evaluates the expression. If the argument represents one or more JavaScript statements, eval performs the statements.

setTimeout(string, millis) / setInterval(string, millis)

Estos métodos tienen la caracteristica de recibir indistintamente una función o un string como primer argumento. Si es un string se evaluara al momento de ejecutarlo (esto es, una vez alcanzado el intervalo definido).

var a = 1;
setTimeout('alert(a)', 0);

new Function()

Si bien en muchos casos su funcionalidad es similar a la de la sentencia eval, tiene sutiles diferencias, como ser el caso de ejecutar el código dentro de un contexto dado (la propia función) y de no generar un clousure del contexto en el cual se instancia.

var a = 1;

var myFun = new Function('alert(a)');
myFun(); // 1

// Funcion anonima inmediatamente ejecutada
(new Function('alert(a)'))(); // myFun(); // 1

En el libro ‘Secrets of the JavaScript Ninja’ de John Resig se ilustra ejemplarmente la diferencia de visibilidad de funciones generadas mediante el uso de eval y del constructor Function, que resumidamente se puede ver aqui:

var target = 1;

var fnEval = (function() {
	var target = 2;
	return eval("false||function(){ console.log(target) }");
})();	

// La funcion genero un clousure con el valor interno
fnEval(); // 2

var fnConst = (function() {
	var target = 2;
	return new Function('console.log(target)');
})();

// La funcion NO genero un clousure con el valor interno
fnConst(); // 1

*A modo de aclaración, la sintaxis ‘false||function‘ es para sobrepasar un bug en Internet Explorer.

Contraindicaciones

Toda la malvada mitología que existe en torno a la evaluación de código tiene sustento, tanto a nivel velocidad (la interpretación de código de manera dinámica es MUCHO más lenta) así también como a nivel seguridad.

Velocidad

Por definición, la evaluación de código siempre será lenta, ya que implica compilar y ejecutar en runtime, situación que escapa a cualquier posible optimización por parte del interprete del browser.

Si bien en el mundillo de la performance y los benchmarks las diferencias que se suelen apreciar no son excesivas, en el caso de uso del eval las diferencias son ABISMALES, con lo cual el impacto real que pueda tener dentro de la performance de un sitio/aplicación es algo a tener realmente en cuenta.

En este benchmark, se puede apreciar la diferencia en cuestión, que graficamente es:

Abismal diferencia en velocidad de acceso

Operaciones por segundo, más es mejor

Seguridad

A diferencia del tema velocidad, los problemas de seguridad varían demasiado según la implementación que se realice con la evaluación de código, en algunos casos esto puede ser realmente crítico.
Obviamente, los riesgos que puede presentar la evaluación de código en el cliente son muchos menores que en servidor, pero aún así existen numerosos ‘exploits’ que pueden ser utilizados gracias a la evaluación de código.

Para interiorizarse en el tema se puede realizar una simple búsqueda en google al respecto.

EcmaScript5, strict mode y eval

El estándar ES5 introduce cambios en cuanto al comportamiento de la sentencia eval bajo modo estricto (“use strict;”). En dicho modo, la sentencia eval no puede incorporar variables al contexto en el cual se lo ejecuta, como si fuese ejecutado en un contexto propio (como lo hace el constructor new Function).*

*Es interesante el caso del indirect eval, aquí explicado por el gran Dmitry A. Soshnikov.

"use strict";

eval("var x = 10; alert(x);"); // 10
alert(x); // "x" is not defined

Cuando reemplazarlo

Como se menciono anteriormente, los casos de uso generales de la sentencia eval suelen ser innecesarios ya que puede lograrse el mismo cometido usando características básicas del lenguaje. El caso típico de esta regla es el de acceso dinámico a las propiedades de un objeto:

var info = {
	band: 'Ozzy Osbourne',
	disc: 'Blizzard of Ozz',
	guitar: 'Randy Rhoads'
};

// Devuelve una key 'al azar'
function getProp() {
    return ['band', 'disc', 'guitar'][+new Date%3];
}

var item;

// Forma incorrecta
item = eval('info.'+getProp());

// Forma aconsejada
var prop = getProp();
item = info[prop]; // Acceso de properties por medio de la notacion alternativa

El uso de las funciones setTimeout y setInterval se soluciona facilmente, llevando el código del string a una función anónima o externa segun corresponda:

// Forma incorrecta
setTimeout('mostrarMensaje()', 100);

// Forma correcta
setTimeout(mostrarMensaje, 100);

// Forma incorrecta
setInterval('total += parcial', 500);

// Forma correcta, utilizando una funcion anonima
setInterval(function() {
	total += parcial;
}, 500);

Por otro lado, la generación de código para su posterior evaluación suele corresponderse con una solución desacertada del problema, siendo MUY pocas las situaciones en las cual no pueda lograrse el objetivo de forma programatica (aún cuando esto implique un desarrollo de mayor complejidad).

Cuando utilizarlo (ejemplos)

Obviamente, hay casos de uso que justifican el uso de evaluación de código. Estos casos son los menos, pero suelen corresponderse con desarrollos muy interesantes, especialmente cuando se trata de ejecutar código compilado de otro lenguaje.

Evaluación de JSON

Ya que la estructura JSON es prácticamente un subset del lenguaje, su intepretación puede realizarse con las técnicas de evaluación de código previamente vistas. Idealmente, esta interpretación se debería dar por medio del API nativa existente en los browsers actuales, la cual es ni más ni menos que una versión nativa del script de Douglas Crockford para dicho proposito.

JSON.js

Obra de Crockford. este script genera un API pública con dos métodos, stringify y parse los cuales convierten un objeto a su representación JSON y viceversa respectivamente. Antes de llegar a la parte de evaluación es necesario validar el string por razones de seguridad, evitando que el JSON en cuestión altere el contexto en el cual se ejecuta:

if (/^[\],:{}\s]*$/
  .test(
    text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
    .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
    .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
	j = eval('(' + text + ')');

	//...

	return j;
}

El código fuente se puede observar en su repositorio público en GitHub: json2.js.

jQuery

Si bien usa un esquema de validaciones similar al de json2.js, la evaluación de código se hace mediante la construcción de una función anónima que devuelve el objeto parseado. El objetivo de dicha diferencia debe estar relacionado con el menor impacto que tiene sobre el uso del eval.

if ( rvalidchars.test( data.replace( rvalidescape, "@" )
	.replace( rvalidtokens, "]" )
	.replace( rvalidbraces, "")) ) {

	return (new Function( "return " + data ))();
}

El código fuente se puede observar en su repositorio público en GitHub: jQuery.

Prototype

A diferencia de json2.js y jQuery, la validación del string a parsear es opcional:

try {
  if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }

El código fuente se puede observar en su repositorio público en GitHub: Prototype.

Compresión y ofuscación:

Packer.js

Otro caso interesante es el del packer generado por Dean Edwards, que permite comprimir y ofuscar un script utilizando un algoritmo similar al de los compresores clásicos.

function(first,second,third) {
	return Math.max.call(null, arguments);
}

Se transforma en:

eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){
while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){
return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp(
'\\b'+e(c)+'\\b','g'),k[c]);return p}('0(1,2,3){4 5.6.7(8,9)}',10,10,
'function|a|b|c|return|Math|max|call|null|arguments'.split('|'),0,{}))

Si bien no es una técnica que tenga en mi aprecio, es destacable el ingenio aplicado a este script.

Interpretación de sintaxis alternativas:

El uso más creativo que puede tener la evaluación de código es la de dar soporte para sintaxis alternativas u otros lenguajes:

CoffeeScript

Para los que no conocen, CoffeeScript es un lenguaje de programación que compila en JavaScript. Obviamente, la compilación in browser que realiza CoffeeScript después de generar el código JS es mediante el uso de la sentencia eval:

# Use standard JavaScript `eval` to eval code.
CoffeeScript.eval = (code, options) ->
  eval CoffeeScript.compile code, options

Esto permite que el código CoffeeScript se pueda compilar a JavaScript legible y correcto (tanto a nivel sintaxis permitida como deseada):

square = (x) -> x * x

Compila en el siguiente código JavaScript:

var square;

square = function(x) {
  return x * x;
};

Procesing.js

Processing en el browser

Processing.js es una librería que permite interpetar Processing (d’oh) directamente en el browser. A modo resumen, Processing es un lenguaje de programación visual, con el cual pueden lograrse espectaculares efectos con una sintaxis simple e intuitiva.

Ejemplo de sintaxis:

int num,cnt,px,py;
Particle[] particles;
boolean initialised=false,doClear=false;
float lastRelease=-1;

void setup() {
  size(1024,600);
  background(255);
  // ....
}

Leer más


Shortlink: http://goo.gl/Z01F9




Discussion has just started! Join in...



Pings to this post

  1. […] scripts malware utilizan la funcion eval() de PHP, una de las mas controvertidas y considerada por maligna  por muchos, ya que abre la posibilidad de ejecutra codigo malicioso y como es bien obvio en estos […]


Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>