¿Por qué se permite ejecutar código Java en comentarios con ciertos caracteres Unicode permitidos?

1356

El siguiente código produce el resultado "¡Hola, mundo!" (No realmente, 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 \u000dcomo 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 "ejecutado".

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

¿Por qué está permitido por la especificación de Java?

Reg
fuente
44
"¿Por qué está permitido esto?" Parece estar demasiado basado en mi opinión. Los diseñadores de idiomas tomaron una decisión, ¿qué más hay que saber? A menos que encuentre una declaración de la persona que toma esa decisión, solo podemos especular.
Ingo Bürk
194
Una cosa interesante es que al menos el IDE de OP, obviamente, se equivoca y muestra resaltado incorrecto,
dhke
14
Posiblemente relacionado: stackoverflow.com/questions/4448180/…
dhke
47
@Tobb Pero los diseñadores de Java están visitando SO, por lo que es posible obtener respuestas de uno de ellos. También pueden existir recursos que ya responden a esta pregunta.
Pshemo
41
La respuesta simple es que el código no está en ningún comentario, según las reglas del lenguaje, por lo que la pregunta está mal formada.
Marqués de Lorne

Respuestas:

741

La decodificación Unicode tiene lugar antes de 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 saber dónde comienzan y terminan los comentarios!

Como se indicó en la Sección 3.3 de JLS, esto permite que cualquier herramienta basada en ASCII procese los archivos fuente:

[...] El lenguaje de programación Java especifica una forma estándar de transformar un programa escrito en Unicode en ASCII que cambia un programa a un formulario que puede ser procesado por herramientas basadas en ASCII. [...]

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

Poder 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 pueda interferir con la semántica de manera tan sutil es solo un efecto secundario (desafortunado).

Hay muchos problemas con este tema y Java Puzzlers de 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 de "Hola mundo").

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 otra manera en su programa. Evítelos en todos los demás casos.


Fuente: Java: ¿Ejecutar código en los comentarios?

aioobe
fuente
84
En resumen, Java lo permite intencionalmente: ¿el "error" está en el IDE del OP?
Betsabé
6060
@Bathsheba: Está más en la cabeza de las personas. La gente no intenta entender cómo funciona el análisis de Java, por lo que los IDE a veces muestran el código de manera incorrecta. En el ejemplo anterior, el comentario debe terminar con \u000dy la parte posterior debe tener resaltados de código.
Aaron Digulla
62
Otro error común es pegar las rutas de Windows en el código, lo // C:\user\...que conduce a un error de compilación ya \userque no es una secuencia de escape Unicode válida.
Aaron Digulla
50
En eclipse, el Código después \u000dse resalta parcialmente. Después de presionar Ctrl + Shift + F, el carácter se reemplaza con una nueva línea y el resto de la línea se
ajusta
20
@TheLostMind Si entiendo la respuesta correctamente, también debería poder reproducirla con comentarios de bloque. \u002A/Debería terminar el comentario.
Taemyr
141

Como esto aún no se ha abordado, aquí hay una explicación de por qué la traducción de los escapes de Unicode ocurre antes de cualquier otro procesamiento del código fuente:

La idea detrás de esto era que permite traducciones sin pérdida de código fuente de Java entre diferentes codificaciones de caracteres. Hoy en día, existe un amplio soporte de Unicode, y esto no parece un problema, pero en aquel entonces no era fácil para un desarrollador de un país occidental recibir algún código fuente de su colega asiático que contenía caracteres asiáticos, hacer algunos cambios ( incluyendo compilarlo 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érdidas, todos los caracteres no admitidos por la codificación de destino son reemplazados por sus escapes Unicode.

Este es un proceso reversible y el punto interesante es que la traducción puede hacerse mediante 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 ella. Esto funciona ya 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 una cantidad arbitraria 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: la \uuuuuuxxxxsintaxis:

Cuando una herramienta de traducción está escapando caracteres y encuentra una secuencia que ya es una secuencia escapada, debe insertar una usecuencia adicional en la secuencia, convirtiéndola \ucafeen \uucafe. El significado no cambia, pero cuando se convierte en la otra dirección, la herramienta solo debe eliminar una uy reemplazar solo las secuencias que contienen una sola upor sus caracteres Unicode. De esa manera, incluso los escapes de Unicode se conservan en su forma original al convertir de ida y vuelta. Supongo que nadie usó esa característica ...

Holger
fuente
1
Curiosamente, native2asciino parece usar la \uu...xxxxsintaxis,
ninjalj
55
Sí, native2asciitenía la intención de ayudar a preparar paquetes de recursos al convertirlos a iso-latin-1, ya que Properties.loadse corrigió para leer solo latin-1. Y allí, las reglas son diferentes, sin \uuu…sintaxis ni etapa de procesamiento temprana. En los archivos de propiedades, property=multi\u000alinees de hecho lo mismo que property=multi\nline. (Contradiciendo a la frase "usar escapes Unicode como se define en la sección 3.3 de La especificación del lenguaje Java ™" de la documentación)
Holger
10
Tenga en cuenta que este objetivo de diseño podría haberse logrado sin ninguna de las verrugas; la forma más fácil hubiera sido prohibir los \uescapes para generar caracteres en el rango U + 0000–007F. (Todos estos caracteres pueden ser representados de forma nativa por todas las codificaciones nacionales que eran relevantes en la década de 1990, bueno, tal vez, excepto algunos de los caracteres de control, pero no es necesario escribir aquellos a Java de todos modos.)
Zwol
3
@zwol: bueno, si excluye los caracteres de control que no están permitidos en el código fuente de Java, tiene razón. Sin embargo, implicaría hacer las reglas más complicadas. Y hoy, es demasiado tarde para discutir la decisión ...
Holger
Ah, el problema de guardar un documento en utf8 y no en latín u otra cosa. Todas mis bases de datos también se rompieron debido a estas tonterías occidentales
David 天宇 Wong
106

Voy a agregar completamente ineficazmente el punto, solo 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, simple y llano, donde sea que ocurra. El formato en la pregunta es engañoso, a lo que corresponde esa secuencia de caracteres sintácticamente es:

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: el código se ejecuta porque no está en un comentario; Está en la línea siguiente. "Ejecutar código en comentarios" no está permitido en Java, como es de esperar.

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

Pepijn Schmitz
fuente
66
Estoy de acuerdo, esto no es un "error de diseño" de Java, pero es un error IDE.
bvdb
3
La pregunta es más bien por qué el código que parece un comentario para alguien que no está familiarizado con este aspecto particular del lenguaje y quizás sin referencia al resaltado de sintaxis, en realidad no es un comentario. La objeción basada en la premisa de que la pregunta es inválida es falsa.
Phil
@ Phil: solo se ve como un comentario cuando se ve con herramientas particulares, otros lo muestran de otra manera.
jmoreno
1
@jmoreno uno no debería tener que tener algo más que un editor de texto para leer el código. Por lo menos, viola el principio de menor sorpresa, a saber, que // los comentarios de estilo continúan hasta el siguiente \ n carácter, no a cualquier otra secuencia que finalmente sea reemplazada por \ n eventualmente. Nunca se espera que los comentarios sean algo más que despojado. Mal preprocesador.
Phil
69

El \u000descape finaliza un comentario porque los \uescapes se convierten uniformemente a los caracteres Unicode correspondientes antes de que el programa se tokenice. Se podría utilizar igualmente \u0057\u0057en lugar de //a comenzar un comentario.

Este es un error en su IDE, que debe sintaxis resaltar la línea para dejar en claro que \u000dfinaliza 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. \uel compilador debe convertir los escapes al carácter Unicode correspondiente solo en contextos donde eso "tiene sentido" (literales de cadena e identificadores, y probablemente en ningún otro lugar) o se les debería haber prohibido generar caracteres en el rango U + 0000–007F , o ambos. Cualquiera de esas semánticas habría evitado que el comentario terminara con el \u000descape, sin interferir con los casos en los que los \uescapes son útiles; tenga en cuenta que eso incluye el uso de \uescapes dentro de los comentarios como una forma de codificar comentarios en un script no latino, porque el editor de texto podría tener una visión más amplia de dónde\ulos escapes son significativos que el compilador. (Sin embargo, no conozco ningún editor o IDE que muestre \uescapes como los caracteres correspondientes en ningún contexto).

Hay un error de diseño similar en la familia C, 1 donde la barra diagonal inversa-nueva línea 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!

Menciono esto para ilustrar que resulta fácil cometer este error de diseño en particular, y no darme cuenta de que es un error hasta que sea demasiado tarde para corregirlo, si estás acostumbrado a pensar en la tokenización y analizar la forma en que piensan los programadores del compilador. sobre tokenización y análisis. Básicamente, si ya ha definido su gramática formal y luego a alguien se le ocurre un caso especial sintáctico: trigrafos, barra invertida-nueva línea, codificación de caracteres Unicode arbitrarios en archivos fuente limitados a ASCII, lo que sea, que necesita ser encajado, es más fácil agregue un pase de transformación antes del tokenizador que 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 estoy inventando esto, de que te permitiría forzar mecánicamente el código de ajuste forzado con líneas arbitrariamente largas en tarjetas perforadas. Todavía era una decisión de diseño incorrecta.

zwol
fuente
17
No iría tan lejos como para decir que es un error de diseño . Podría estar de acuerdo con usted en que fue una elección de diseño deficiente, o una elección con consecuencias desafortunadas, pero sigo pensando que funciona como lo diseñaron los diseñadores de lenguaje: le permite usar cualquier carácter unicode en cualquier parte del archivo, mientras mantiene la codificación ASCII del archivo
aioobe
12
Dicho esto, creo que la elección de la etapa de procesamiento \ufue menos absurda que la decisión de seguir el ejemplo de C al usar ceros iniciales para la notación octal. Si bien la notación octal a veces es útil, todavía no he escuchado a nadie articular un argumento de por qué un cero inicial es una buena manera de indicarlo.
supercat
3
@supercat Las personas que lanzaron esa característica en C89 estaban generalizando el comportamiento del preprocesador K&R original en lugar de diseñar una característica desde cero. Dudo que estén familiarizados con las mejores prácticas de tarjetas perforadas, y también dudo que la función se haya utilizado alguna vez para su propósito declarado, excepto tal vez para uno o dos ejercicios de retrocomputación.
zwol
8
@supercat No tendría un problema con Java \ucomo transformación previa a la tokenización si estuviera prohibido producir caracteres en el rango U + 0000..U + 007F. Es la combinación de "esto funciona en todas partes" y "este alias de caracteres ASCII con significado sintáctico" lo que lo degrada de incómodo a completamente equivocado.
zwol
44
En su "para pedantes": Por supuesto en ese momento el //comentario de una sola línea no existía . Y dado que C tiene un terminador de declaración que no es una línea nueva, se usaría principalmente para cadenas largas, excepto que, por lo que puedo determinar, la "concatenación literal de cadenas" estaba allí desde K&R.
Mark Hurd
22

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

Para aquellas personas que preguntan "¿quién quiere escapar de Unicode en los comentarios?", Supongo que son personas cuya lengua materna utiliza el conjunto de caracteres latinos. En otras palabras, es inherente al diseño original de Java que la gente pueda usar caracteres Unicode arbitrarios donde sea legal en un programa Java, más típicamente en comentarios y cadenas.

Podría decirse que es una deficiencia en los programas (como IDE) que se utilizan para ver el texto fuente de que dichos programas no pueden interpretar los escapes de Unicode y mostrar el glifo correspondiente.

Jonathan Gibbons
fuente
8
Hoy en día usamos UTF-8 para nuestro código fuente, y podemos usar los caracteres Unicode directamente, sin necesidad de escapes.
Paŭlo Ebermann
21

Estoy de acuerdo con @zwol en que esto es un error de diseño; pero lo critico aún más.

\uescape es útil en cadenas y char literales; y ese es el único lugar donde debería existir. Debe manejarse de la misma manera que otros escapes como \n; y "\u000A" debe significar exactamente "\n".

No tiene ningún sentido tener \uxxxxcomentarios, nadie puede leer eso.

Del mismo modo, no tiene sentido usarlo \uxxxxen 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 es la última vez que hemos visto eso?

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

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

ZhongYu
fuente
55
Supongo que no estás dando vueltas, donde se usan caracteres no ASCII en las API. Hay personas que lo usan (no yo), por ejemplo, en países asiáticos. Y cuando utiliza caracteres no ASCII en los identificadores, tiene poco sentido prohibirlos en los comentarios de la documentación. Sin embargo, permitirles dentro de una ficha y permitirles cambiar el significado o el límite de una ficha son cosas diferentes.
Holger
15
pueden usar la codificación de archivo adecuada. ¿Por qué escribir int \u5431cuando puedes hacerlo?int 整
ZhongYu
3
¿Qué vas a hacer cuando se tiene que compilar el código en contra de su API y no puede utilizar la codificación correcta (suponiendo que no había generalizado UTF-8apoyo en 1995). Solo tiene que llamar a un método y no desea instalar el paquete de soporte de idiomas asiáticos de su sistema operativo (recuerde, los noventa) para ese método único ...
Holger
55
Lo que está mucho más claro ahora que en 1995 es que mejor sabes inglés si quieres programar. La programación es una interacción internacional, y casi todos los recursos están en inglés.
ZhongYu
8
No creo que esto haya cambiado. La documentación de Java también estaba en inglés la mayor parte del tiempo. Hubo una traducción al japonés mantenida por un tiempo, pero mantener dos idiomas no respalda realmente la idea de mantenerla para todos los lugares del mundo (más bien la refutó). Y antes de eso, de todos modos, no había lenguaje convencional con soporte Unicode en los identificadores. Supongo que alguien pensó que el siguiente código fuente era el código fuente localizado. Diría afortunadamente que no despegó.
Holger
11

Las únicas personas que pueden responder por qué se implementaron los escapes de Unicode tal como fueron son 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. Sin embargo, esto presenta un problema:

  • Desea poder usar cualquier carácter BMP.
  • Desea poder ingresar cualquier carácter BMP razonablemente fácil. Una forma de hacerlo es con escapes Unicode.
  • Desea mantener la especificación léxica fácil de leer y escribir para los humanos, y razonablemente fácil de implementar también.

Esto es increíblemente difícil cuando los escapes de Unicode entran en juego: crea una carga completa de nuevas reglas lexer.

La salida fácil es hacer lexing en dos pasos: primero busque y reemplace todos los escapes Unicode con el carácter que representa, y luego analice 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.

Martijn
fuente
2
O restrinja el uso de \ uxxxx a identificadores, literales de cadena y constantes de caracteres. Que es lo que hace C11.
ninjalj
sin embargo, eso realmente complica las reglas del analizador sintético, porque eso es lo que define esas cosas, y eso es lo que estoy especulando es parte de la razón por la que es así.
Martijn