Alcance inconsistente de "uso estricto" en diferentes navegadores web (en relación con los argumentos.llamado y llamante)


Situación:

He encontrado algo extraño con respecto a modo estricto en Javascript.

  • Estoy usando una biblioteca Javascript externa de terceros que
    • fue minificado,
    • tiene más de 4000 líneas de código,
    • es no usando use strict en absoluto, y
    • está usando arguments.callee.
  • Estoy usando use strict en mi propio código, dentro del ámbito de una función.

Cuando llamo a una de las funciones proporcionadas por el biblioteca, lanza un error. Sin embargo,

  • el error se genera solo si estoy usando use strict
  • el error se lanza en todos los navegadores excepto Chrome

Código:

He eliminado todas las cosas no relacionadas y reducido el código a esta (demo en línea en jsFiddle):

// This comes from the minified external JS library.
// It creates a global object "foo".
(function () {
    foo = {};
    foo.bar = function (e) {
        return function () {
            var a5 = arguments.callee;
            while (a5) {
                a5 = a5.caller      // Error on this line in all browsers except Chrome
            }
        }
    }("any value here");
})();

// Here's my code.
(function() {
    "use strict";   // I enable strict mode in my own function only.

    foo.bar();
    alert("done");
})();

Resultado de la prueba:

+-----------------------+-----+--------------------------------------------------------------+
| Browser               | OS  | Error                                                        |
+-----------------------+-----+--------------------------------------------------------------+
| Chrome 27.0.1453.94 m | Win | <<NO ERROR!>>                                                |
| Opera 12.15           | Win | Unhandled Error: Illegal property access                     |
| Firefox 21.0          | Win | TypeError: access to strict mode caller function is censored |
| Safari 5.1.7          | Win | TypeError: Type error                                        |
| IE 10                 | Win | SCRIPT5043: Accessing the 'caller' property of a function or |
|                       |     |             arguments object is not allowed in strict mode   |
| Chrome 27.0.1543.93   | Mac | <<NO ERROR!>>                                                |
| Opera 12.15           | Mac | Unhandled Error: Illegal property access                     |
| Firefox 21.0          | Mac | TypeError: access to strict mode caller function is censored |
| Safari 6.0.4          | Mac | TypeError: Function.caller used to retrieve strict caller    |
+-----------------------+-----+--------------------------------------------------------------+

Nota: para OS, Win = Windows 7, Mac = Mac OS 10.7.5


Mi comprensión:


Pregunta:

Por lo tanto, son todos los navegadores excepto Chrome mal? O es al revés? ¿O es este comportamiento indefinido por lo que los navegadores pueden optar por implementarlo de cualquier manera?

Author: Community, 2013-06-01

1 answers

Prefacio

Un par de puntos rápidos antes de entrar en la carne de esto:{[45]]}

  • Todos los navegadores de escritorio modernos soportan use strict...

No, para nada. IE8 es un navegador bastante moderno (ya no, en 2015), y IE9 es un bastante navegador bastante moderno. Ninguno de ellos soporta el modo estricto (IE9 soporta partes del mismo). IE8 va a estar con nosotros mucho tiempo, porque es tan alto como se puede ir en Windows XP. A pesar de que XP ahora está completamente al final de su vida útil (bueno, puede comprar un plan especial de "Soporte personalizado" de MS), la gente continuará usándolo por un tiempo.

  • El use strict está dentro de mi función, por lo que todo lo definido fuera de su ámbito no se ve afectado{[49]]}

No del todo. La especificación impone restricciones sobre cómo incluso el código no estricto usa funciones creadas en modo estricto. El modo tan estricto puede alcanzar fuera de su caja. Y de hecho, eso es parte de lo que seguir con el código que estás usando.

Descripción general

Por lo tanto, son todos los navegadores excepto Chrome mal? O es al revés? ¿O es este comportamiento indefinido por lo que los navegadores pueden optar por implementarlo de cualquier manera?

Mirando un poco, parece: {[45]]}

  1. Chrome lo está haciendo bien de una manera,

  2. Firefox lo está haciendo bien de una manera diferente,

  3. ...y IE10 lo está consiguiendo muy ligeramente equivocado. :- ) (IE9 definitivamente se equivoca, aunque no de una manera particularmente dañina.)

No miré a los demás, pensé que habíamos cubierto el suelo.

El código que causa fundamentalmente el problema es este bucle

var a5 = arguments.callee;
while (a5) {
    a5 = a5.caller      // Error on this line in all browsers except Chrome
}

...que se basa en la propiedad caller de los objetos de función. Así que empecemos por ahí.

Function#caller

La propiedad Function#caller nunca fue definida en la especificación de la 3a edición. Algunas implementaciones proporcionadas es una idea sorprendentemente mala (lo siento, eso fue subjetivo, ¿no?) un problema de implementación (incluso más de uno que arguments.caller), particularmente en entornos multihilo (y hay motores JavaScript multihilo), así como con código recursivo, como señaló Bergi en los comentarios sobre la pregunta.

Así que en la 5a edición se deshicieron explícitamente de ella, especificando que hacer referencia a la propiedad caller en una función estricta arrojaría error. (Esto es en §13.2, Creando Objetos de Función , Paso 19.)

Eso está en una función estricta. Sin embargo, en una función no estricta, el comportamiento no está especificado y depende de la implementación. Es por eso que hay tantas maneras diferentes de hacer esto bien.

Código instrumentado

Es más fácil hacer referencia a código instrumentado que a una sesión de depuración, así que usemos esto :

console.log("1. Getting a5 from arguments.callee");
var a5 = arguments.callee;
console.log("2. What did we get? " +
            Object.prototype.toString.call(a5));
while (a5) {
    console.log("3. Getting a5.caller");
    a5 = a5.caller;      // Error on this line in all browsers except Chrome
    console.log("4. What is a5 now? " +
                Object.prototype.toString.call(a5));
}

Cómo Chrome Lo Consigue Derecha

En V8 (motor JavaScript de Chrome), el código anterior nos da esto:

1. Getting a5 from arguments.callee
2. What did we get? [object Function]
3. Getting a5.caller
4. What is a5 now? [object Null]

Así que obtuvimos una referencia a la función foo.bar de arguments.callee, pero luego acceder a caller en esa función no estricta nos dio null. El bucle termina y no obtenemos ningún error.

Dado que Function#caller no está especificado para funciones no estrictas, a V8 se le permite hacer lo que quiera para ese acceso a caller en foo.bar. Regresar null es perfectamente razonable (aunque me sorprendió ver null en lugar de undefined). (Volveremos a eso null en las conclusiones a continuación...)

Cómo Firefox Lo Hace Bien

SpiderMonkey (motor JavaScript de Firefox) hace esto:

1. Getting a5 from arguments.callee
2. What did we get? [object Function]
3. Getting a5.caller
TypeError: access to strict mode caller function is censored

Empezamos obteniendo foo.bar desde arguments.callee, pero luego accediendo a caller en esa función no estricta falla con un error.

Dado que, de nuevo, el acceso a caller en una función no estricta es un comportamiento no especificado, la gente de SpiderMonkey puede hacer lo que quiera. Decidieron lanzar un error si la función que se devolvería es una función estricta. Una línea fina, pero como no se especifica, se les permite caminar por ella.

Cómo IE10 Se Equivoca Un Poco

JScript (motor JavaScript de IE10) hace esto:

 1. Getting a5 from arguments.callee 
 2. What did we get? [object Function] 
 3. Getting a5.caller 
SCRIPT5043: Accessing the 'caller' property of a function or arguments object is not allowed in strict mode

Al igual que con los demás, obtenemos la función foo.bar de arguments.callee. Entonces tratar de acceder a esa función no estricta caller nos da un error diciendo que no podemos hacer eso en modo estricto.

Yo llamo a esto " incorrecto "(pero con un muy "w" minúscula) porque dice que no podemos hacer lo que estamos haciendo en modo estricto, pero no estamos en modo estricto.

Pero se podría argumentar que esto no es más malo que lo que Chrome y Firefox hacen, porque (de nuevo) el acceso caller es un comportamiento no especificado. Así que la gente de IE10 decidió que su implementación de este comportamiento no especificado arrojaría un error de modo estricto. Creo que es engañoso, pero de nuevo, si es "incorrecto", ciertamente no es muy{[56]]} Equivocada.

POR cierto, IE9 definitivamente se equivoca:

1. Getting a5 from arguments.callee 
2. What did we get? [object Function] 
3. Getting a5.caller 
4. What is a5 now? [object Function] 
3. Getting a5.caller 
4. What is a5 now? [object Null]

Permite Function#caller en la función no estricta, y luego lo permite en una función estricta, devolviendo null. La especificación es clara de que ese segundo acceso debería haber arrojado un error, ya que estaba accediendo a caller en una función estricta.

Conclusiones y observaciones

Lo interesante de todo lo anterior es que además del comportamiento claramente especificado de lanzar un error si intenta acceder a caller en funciones estrictas, Chrome, Firefox e IE10 (de varias maneras) evitan que uses caller para obtener una referencia a una función estricta, incluso cuando accedes a caller en una función no estricta. Firefox hace esto lanzando un error. Chrome e IE10 lo hacen devolviendo null. Todos ellos admiten obtener una referencia a una función non-strict a través de caller (en una función no estricta), solo que no es una función estricta.

No puedo encontrar ese comportamiento especificado en ninguna parte (pero entonces, caller en las funciones no estrictas no están especificadas en absoluto...). Probablemente sea lo Correcto(tm), simplemente no lo veo especificado.

Este código también es divertido para jugar: Live Copy | Fuente en vivo

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Strict and Loose Function#caller</title>
  <style>
    p {
      font-family: sans-serif;
      margin: 0.1em;
    }
    .err {
      color: #d00;
    }
  </style>
</head>
<body>
  <script>
    function display(msg, cls) {
        var p = document.createElement('p');
        if (cls) {
            p.className = cls;
        }
        p.innerHTML = String(msg);
        document.body.appendChild(p);
    }

    // The loose functions
    (function () {
      function loose1() {
        display("loose1 calling loose2");
        loose2();
      }
      loose1.id = "loose1"; // Since name isn't standard yet

      function loose2() {
        var c;

        try {
          display("loose2: looping through callers:");
          c = loose2;
          while (c) {
            display("loose2: getting " + c.id + ".caller");
            c = c.caller;
            display("loose2: got " +
                    ((c && c.id) || Object.prototype.toString.call(c)));
          }
          display("loose2: done");
        }
        catch (e) {
          display("loose2: exception: " +
                  (e.message || String(e)),
                  "err");
        }
      }
      loose2.id = "loose2";

      window.loose1 = loose1;

      window.loose2 = loose2;
    })();

    // The strict ones
    (function() {
      "use strict";

      function strict1() {
        display("strict1: calling strict2");
        strict2();
      }
      strict1.id = "strict1";

      function strict2() {
        display("strict2: calling loose1");
        loose1();
      }
      strict2.id = "strict2";

      function strict3() {
        display("strict3: calling strict4");
        strict4();
      }
      strict3.id = "strict3";

      function strict4() {
        var c;

        try {
          display("strict4: getting strict4.caller");
          c = strict4.caller;
        }
        catch (e) {
          display("strict4: exception: " +
                  (e.message || String(e)),
                 "err");
        }
      }
      strict4.id = "strict4";

      strict1();      
      strict3();
    })();
  </script>
</body>
</html>
 39
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-13 08:34:22