Pregunta ¿Por qué se está ejecutando código Java en comentarios con ciertos caracteres Unicode permitidos?


El siguiente código produce la salida "Hello World!" (en realidad no, pruébalo).

public static void main(String... args) {

   // The comment below is not a typo.
   // \u000d System.out.println("Hello World!");
}

La razón de esto es que el compilador de Java analiza el carácter Unicode \u000d como una nueva línea y se transforma en:

public static void main(String... args) {

   // The comment below is not a typo.
   //
   System.out.println("Hello World!");
}

Por lo tanto, resulta en un comentario que se "ejecuta".

Dado que esto se puede usar para "ocultar" código malicioso o lo que sea que un mal programador pueda concebir, por qué está permitido en los comentarios?

¿Por qué esto es permitido por la especificación de Java?


1247
2018-06-09 09:02


origen


Respuestas:


La decodificación Unicode tiene lugar antes que cualquier otra traducción léxica. El beneficio clave de esto es que hace que sea trivial ir y venir entre ASCII y cualquier otra codificación. ¡Ni siquiera necesita averiguar dónde comienzan y terminan los comentarios!

Como se indica en Sección 3.3 de JLS esto permite que cualquier herramienta basada en ASCII pueda procesar los archivos fuente:

[...] El lenguaje de programación Java especifica una forma estándar de la transformación de un programa escrito en Unicode a ASCII que cambia de un programa en una forma que pueda ser procesada por herramientas basadas en ASCII. [...]

Esto proporciona una garantía fundamental para la independencia de la plataforma (independencia de los conjuntos de caracteres admitidos) que siempre ha sido un objetivo clave para la plataforma Java.

Ser capaz de escribir cualquier carácter Unicode en cualquier parte del archivo es una característica interesante, y especialmente importante en los comentarios, al documentar código en idiomas no latinos. El hecho de que puede interferir con la semántica de una manera tan sutil es sólo una (lamentable) de efectos secundarios.

Hay muchos errores en este tema y Puzzles de Java por Joshua Bloch y Neal Gafter incluyeron la siguiente variante:

¿Es este un programa legal de Java? Si es así, ¿qué imprime?

\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020
\u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079
\u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020
\u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063
\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028
\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020
\u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b
\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074
\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020
\u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b
\u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d

(Este programa resulta ser un simple programa "Hello World").

En la solución al rompecabezas, señalan lo siguiente:

Más en serio, este rompecabezas sirve para reforzar las lecciones de los tres anteriores: Los escapes Unicode son esenciales cuando necesita insertar caracteres que no se pueden representar de ninguna otra forma en su programa. Evítelos en todos los demás casos.


Fuente: Java: ¿ejecutando el código en los comentarios?


687
2018-06-09 09:13



Como esto no ha abordado sin embargo, aquí una explicación, ¿por qué la traducción de Unicode escapa ocurre antes de cualquier otro procesamiento de código fuente:

La idea detrás de esto era que permite traducciones sin pérdidas de código fuente Java entre diferentes codificaciones de caracteres. Hoy en día, existe un apoyo generalizado Unicode, y esto no se ve como un problema, pero en aquel entonces no era fácil para un desarrollador de un país occidental para recibir algo de código fuente de su colega de Asia que contiene caracteres asiáticos, hacer algunos cambios ( incluyendo compilar y probarlo) y devolver el resultado, todo sin dañar algo.

Por lo tanto, el código fuente de Java se puede escribir en cualquier codificación y permite una amplia gama de caracteres dentro de identificadores, caracteres y Stringliterales y comentarios. Luego, para transferirlo sin pérdida, todos los caracteres que no son compatibles con la codificación objetivo se reemplazan por sus escapes Unicode.

Este es un proceso reversible y lo interesante es que la traducción se puede realizar con una herramienta que no necesita saber nada sobre la sintaxis del código fuente de Java, ya que la regla de traducción no depende de él. Esto funciona a medida que la traducción a sus caracteres Unicode reales dentro del compilador también ocurre independientemente de la sintaxis del código fuente de Java. Implica que puede realizar un número arbitrario de pasos de traducción en ambas direcciones sin cambiar el significado del código fuente.

Esta es la razón de otra característica extraña que ni siquiera ha mencionado: el \uuuuuuxxxx sintaxis:

Cuando una herramienta de traducción escapa de los caracteres y encuentra una secuencia que ya es una secuencia de escape, debe insertar un u en la secuencia, convirtiendo \ucafe a \uucafe. El significado no cambia, pero al convertirlo en la otra dirección, la herramienta solo debe eliminar uno u y reemplaza solo las secuencias que contienen un solo u por sus caracteres Unicode. De esta forma, incluso los escapes de Unicode se conservan en su forma original al convertir de ida y vuelta. Supongo que nadie usó esa característica ...


132
2018-06-09 17:59



Agregaré el punto de manera totalmente ineficaz, simplemente porque no puedo evitarlo y no lo he visto aún, que la pregunta no es válida, ya que contiene una premisa oculta que es incorrecta, a saber, que el código está en ¡un comentario!

En Java el código fuente \ u000d es equivalente en todos los sentidos a un carácter ASCII CR. Es un final de línea, claro y simple, donde sea que ocurra. El formato de la pregunta es engañoso, a lo que esa secuencia de caracteres corresponde sintácticamente es a:

public static void main(String... args) {
   // The comment below is no typo. 
   // 
 System.out.println("Hello World!");
}

En mi humilde opinión, la respuesta más correcta es por lo tanto: el código se ejecuta porque no está en un comentario; está en la siguiente línea. "Ejecutar código en comentarios" no está permitido en Java, como cabría esperar.

Gran parte de la confusión proviene del hecho de que los marcadores de sintaxis y los IDE no son lo suficientemente sofisticados como para tener en cuenta esta situación. O bien no procesan los escapes Unicode en absoluto, o lo hacen después de analizar el código en lugar de antes, como javac hace.


97
2018-06-10 17:37



los \u000d escape finaliza un comentario porque \u los escapes se convierten uniformemente a los caracteres Unicode correspondientes antes de el programa es simbólico Podrías igualmente usar \u0057\u0057 en lugar de // a empezar un comentario.

Este es un error en su IDE, que debería sintaxis: resaltar la línea para dejar en claro que \u000d finaliza el comentario.

Esto también es un error de diseño en el lenguaje. No se puede corregir ahora, porque eso rompería los programas que dependen de él. \u los escapes deben ser convertidos al carácter Unicode correspondiente por el compilador solo en contextos donde "tenga sentido" (literales e identificadores de cadena, y probablemente en ningún otro lugar) o que tengan prohibido generar caracteres en el rango U + 0000-007F , o ambos. Cualquiera de esas semánticas habría impedido que el comentario fuera terminado por el \u000descapar, sin interferir con los casos donde \u escapes son útiles; tenga en cuenta que incluye uso de \u se escapa dentro de los comentarios como una forma de codificar comentarios en un guión no latino, porque el editor de texto podría tener una visión más amplia de dónde \u los escapes son importantes que el compilador. (No conozco ningún editor o IDE que muestre \u escapa como los personajes correspondientes en alguna contexto, sin embargo)

Hay un error de diseño similar en la familia C,1 donde backslash-newline se procesa antes de que se determinen los límites de los comentarios, por ejemplo,

// this is a comment \
   this is still in the comment!

Lo menciono para ilustrar que es fácil hacer este error de diseño en particular, y no me doy cuenta de que es un error hasta que sea demasiado tarde para corregirlo, si estás acostumbrado a pensar en tokenización y analizar la forma en que los programadores de compilación piensan sobre tokenización y análisis. Básicamente, si ya ha definido su gramática formal y luego alguien presenta un caso sintáctico especial - trigrafos, barra diagonal inversa - línea nueva, codificación de caracteres Unicode arbitrarios en archivos fuente limitados a ASCII, lo que sea - que debe ser insertado, es más fácil agregar un pase de transformación antes de el tokenizador de lo que es redefinir el tokenizador para prestar atención a dónde tiene sentido usar ese caso especial.

1 Para los pedantes: soy consciente de que este aspecto de C fue 100% intencional, con la razón, no me lo estoy inventando, de que te permitiría ajustar mecánicamente el código con líneas arbitrariamente largas en tarjetas perforadas. Todavía era una decisión de diseño incorrecta.


63
2018-06-09 15:16



Esta fue una elección de diseño intencional que se remonta al diseño original de Java.

Para aquellos que preguntan "¿quién quiere escapar de Unicode en los comentarios?", Supongo que son personas cuyo idioma nativo usa el conjunto de caracteres latinos. En otras palabras, es inherente al diseño original de Java que la gente pueda usar caracteres Unicode arbitrarios siempre que sean legales en un programa Java, más típicamente en comentarios y cadenas.

Podría decirse que es un defecto en los programas (como IDEs) que se utilizan para ver el texto de origen que dichos programas no pueden interpretar los escapes Unicode y mostrar el glifo correspondiente.


21
2018-06-09 18:45



Estoy de acuerdo con @zwol en que este es un error de diseño; pero soy aún más crítico con eso.

\u escape es útil en cadenas y literales char; y ese es el único lugar donde debería existir. Se debe manejar de la misma manera que otras escapes como \n; y "\u000A"  debería significa exactamente "\n".

No tiene ningún sentido tener \uxxxx en los comentarios, nadie puede leer eso.

Del mismo modo, no tiene sentido usar \uxxxx en otra parte del programa. La única excepción es probablemente en las API públicas que están obligadas a contener algunos caracteres no ascii: ¿cuál fue la última vez que hemos visto eso?

Los diseñadores tenían sus razones en 1995, pero 20 años después, esta parece ser una elección incorrecta.

(pregunta a los lectores: ¿por qué esta pregunta sigue recibiendo nuevos votos? ¿esta pregunta está vinculada desde algún lugar popular?)


21
2018-06-09 16:47



Las únicas personas que pueden responder por qué Unicode escapa fueron implementadas como lo fueron las personas que escribieron la especificación.

Una razón plausible para esto es que existía el deseo de permitir que todo el BMP fuera posible como caracteres del código fuente de Java. Esto presenta un problema sin embargo:

  • Desea poder usar cualquier personaje BMP.
  • Desea poder ingresar cualquier chapaleta BMP de forma razonablemente fácil. Una forma de hacerlo es con Unicode escapes.
  • Desea que las especificaciones léxicas sean fáciles de leer y escribir para los seres humanos, y también razonablemente fáciles de implementar.

Esto es increíblemente difícil cuando Unicode escapa entra en la refriega: crea una carga completa de nuevas reglas lexer.

La salida más fácil es hacer el léxico en dos pasos: primero buscar y reemplazar todos los escapes Unicode con el carácter que representa, y luego analizar el documento resultante como si los escapes Unicode no existieran.

La ventaja de esto es que es fácil de especificar, por lo que simplifica la especificación y es fácil de implementar.

La desventaja es, bueno, tu ejemplo.


11
2018-06-12 11:59



El compilador no solo traduce los escapes Unicode en los caracteres que representan antes de analizar un programa en tokens, sino que lo hace antes de descartar los comentarios y el espacio en blanco.

Este programa contiene un único escape Unicode (\ u000d), ubicado en su único comentario. Como dice el comentario, este escape representa el carácter de salto de línea, y el compilador lo traduce debidamente antes de descartar el comentario.

Esto depende de la plataforma. En ciertas plataformas, como UNIX, funcionará; en otros, como Windows, no lo hará. Aunque la salida puede parecer igual a simple vista, fácilmente podría causar problemas si se guardara en un archivo o se transfiriera a otro programa para su posterior procesamiento.


1
2017-11-02 13:01