web-dev-qa-db-fra.com

Pourquoi l'exécution de code Java dans des commentaires avec certains caractères Unicode est-elle autorisée?

Le code suivant produit la sortie "Hello World!" (non vraiment, essayez-le).

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

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

La raison en est que le compilateur Java analyse le caractère Unicode \u000d comme une nouvelle ligne et se transforme en:

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

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

Il en résulte un commentaire "exécuté".

Puisque ceci peut être utilisé pour "cacher" un code malveillant ou tout ce qu'un programmeur maléfique peut concevoir, pourquoi est-il permis dans les commentaires?

Pourquoi est-ce autorisé par la spécification Java?

1315
Reg

Le décodage Unicode a lieu avant toute autre traduction lexicale. Le principal avantage de cette méthode est qu’il est très facile de faire des allers-retours entre ASCII et n’importe quel autre codage. Vous n'avez même pas besoin de savoir où commencent et où finissent les commentaires!

Comme indiqué dans JLS Section 3. , cela permet à tout outil basé sur ASCII de traiter les fichiers source:

[...] Le langage de programmation Java spécifie un moyen standard de transformer un programme écrit en Unicode en ASCII qui transforme un programme en un formulaire pouvant être traité par des outils ASCII. . [...]

Cela donne une garantie fondamentale pour l'indépendance de la plate-forme (indépendance des jeux de caractères pris en charge), qui a toujours été un objectif clé de la plate-forme Java.

Etre capable d'écrire n'importe quel caractère Unicode n'importe où dans le fichier est une fonctionnalité intéressante, et particulièrement importante dans les commentaires, lors de la documentation de code dans des langues non latines. Le fait qu'il puisse interférer avec la sémantique de manière aussi subtile n'est qu'un effet secondaire (malheureux).

Il y a beaucoup de pièges sur ce thème et Java Puzzlers par Joshua Bloch et Neal Gafter ont inclus la variante suivante:

Est-ce un programme légal Java? Si oui, qu'est-ce qu'il 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

(Ce programme s'avère être simplement un programme "Hello World".)

Dans la solution au casse-tête, ils soulignent les points suivants:

Plus sérieusement, ce puzzle renforce les leçons des trois précédentes: Les échappements Unicode sont essentiels lorsque vous devez insérer des caractères qui ne peuvent pas être représentés autrement dans votre programme. Evitez-les dans tous les autres cas.


Source: Java: Exécuter du code dans les commentaires?!

724
aioobe

Comme cela n’a pas encore été résolu, voici une explication de la traduction des échappements Unicode avant tout autre traitement de code source:

L'idée sous-jacente était qu'elle permettait des traductions sans perte du code source Java entre différents codages de caractères. Aujourd'hui, le support Unicode est répandu, et cela ne semble pas être un problème, mais à l'époque, il n'était pas facile pour un développeur d'un pays occidental de recevoir du code source de son collègue asiatique contenant des caractères asiatiques. compiler et tester) et renvoyer le résultat, sans endommager quoi que ce soit.

Ainsi, le code source Java peut être écrit dans n’importe quel codage et autorise un large éventail de caractères dans les identificateurs, les littéraux et les commentaires String. Ensuite, afin de le transférer sans perte, tous les caractères non pris en charge par le codage cible sont remplacés par leurs échappements Unicode.

C’est un processus réversible et le point intéressant est que la traduction peut être effectuée par un outil qui n’a pas besoin de connaître la syntaxe du code source Java, car la règle de traduction n’en dépend pas. Cela fonctionne comme la traduction en leurs caractères Unicode réels dans le compilateur se produit indépendamment de la syntaxe de code source Java. Cela implique que vous puissiez effectuer un nombre arbitraire d'étapes de traduction dans les deux sens sans jamais changer la signification du code source.

C’est la raison d’une autre caractéristique étrange qui n’a même pas été mentionnée: la syntaxe \uuuuuuxxxx:

Lorsqu'un outil de traduction utilise des caractères d'échappement et rencontre une séquence qui est déjà une séquence d'échappement, il doit insérer un u supplémentaire dans la séquence, en convertissant \ucafe en \uucafe. La signification ne change pas, mais lors de la conversion dans l’autre sens, l’outil doit simplement supprimer un u et ne remplacer que les séquences contenant un seul u par leurs caractères Unicode. Ainsi, même les échappements Unicode sont conservés dans leur forme d'origine lors de la conversion. Je suppose que personne n'a jamais utilisé cette fonctionnalité…

137
Holger

Je vais ajouter le point de manière totalement inefficace, simplement parce que je ne peux pas m'en empêcher et que je ne l'ai pas encore vu, que la question est invalide car elle contient une prémisse cachée qui est fausse, à savoir que le code est en un commentaire!

Dans Java, le code source\u000d est équivalent à un caractère ASCII CR. C'est une fin de ligne, claire et simple, où qu'elle se produise. La mise en forme de la question est trompeuse. La syntaxe de cette séquence de caractères est la suivante:

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

IMHO la réponse la plus correcte est donc: le code s'exécute parce qu'il n'est pas dans un commentaire; c'est sur la ligne suivante. "L'exécution de code dans les commentaires" n'est pas autorisée en Java, comme on peut s'y attendre.

Une grande partie de la confusion provient du fait que les surligneurs de syntaxe et les IDE ne sont pas assez sophistiqués pour prendre en compte cette situation. Soit ils ne traitent pas du tout les échappements unicode, soit ils le font après avoir analysé le code au lieu d’avant, comme le fait javac.

102
Pepijn Schmitz

Le \u000d escape met fin à un commentaire car \u les échappements sont convertis uniformément en caractères Unicode correspondants avant le programme est symbolisé. Vous pouvez également utiliser \u0057\u0057 au lieu de // pour commencer un commentaire.

Il s'agit d'un bogue dans votre IDE, qui doit être mis en évidence par la syntaxe pour indiquer que le \u000d termine le commentaire.

C'est aussi une erreur de conception dans le langage. On ne peut pas le corriger maintenant, car cela briserait les programmes qui en dépendent. \u les caractères d'échappement doivent être convertis en caractère Unicode correspondant par le compilateur uniquement dans des contextes où cela "a du sens" (littéraux de chaîne et identificateurs, et probablement nulle part ailleurs), ou il aurait été interdit de générer des caractères dans le U. + 0000–007F, ou les deux. L'une ou l'autre de ces sémantiques aurait empêché le commentaire d'être terminé par le caractère d'échappement \u000d, sans interférer avec les cas où \u échappements sont utiles - notez que includes utilisation de \u échappe à l'intérieur des commentaires pour coder des commentaires dans un script non latin, car l'éditeur de texte pourrait avoir une vue plus large de l'endroit où \u les échappées sont plus importantes que le compilateur. (Je ne suis au courant d'aucun éditeur ou IDE qui afficherait \u s'échappe comme caractères correspondants dans le contexte tous.)

Il y a une erreur de conception similaire dans la famille C,1 où la barre oblique inverse-nouvelle ligne est traitée avant que les limites des commentaires ne soient déterminées, donc par ex.

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

Je soulève cette question pour illustrer le fait qu’il est facile de commettre cette erreur de conception particulière et de ne pas me rendre compte que c’est une erreur jusqu’à ce qu’il soit trop tard pour la corriger, si vous êtes habitué à penser à la segmentation et à l’analyse de la façon dont les programmeurs de compilateurs pensent. à propos de tokenization et d'analyse. En gros, si vous avez déjà défini votre grammaire formelle et que quelqu'un vous présente un cas syntaxique particulier - trigraphs, backslash-newline, encodage de caractères Unicode arbitraires dans des fichiers source limités à ASCII, peu importe - qui doit être calé, il est plus facile de ajoutez une passe de transformation avant le tokenizer que pour le redéfinir afin de faire attention à l'endroit où il est judicieux d'utiliser ce cas particulier.

1 Pour les pédants: je suis conscient que cet aspect du C était 100% intentionnel, avec la justification - je ne l'invente pas - que cela vous permettrait de forcer mécaniquement le code avec des lignes arbitrairement longues sur des cartes perforées. C'était toujours une décision de conception incorrecte.

65
zwol

C'était un choix de conception intentionnel qui remonte à la conception originale de Java.

À ceux qui se demandent "qui veut que Unicode échappe aux commentaires?", Je présume que ce sont des gens dont la langue maternelle utilise le jeu de caractères latin. En d’autres termes, il est inhérent à la conception originale de Java que les utilisateurs puissent utiliser des caractères Unicode arbitraires chaque fois que cela est légal dans un programme Java, le plus souvent dans des commentaires et des chaînes.

On peut soutenir que les programmes (comme les IDE) utilisés pour afficher le texte source sont incapables d'interpréter les échappements Unicode et d'afficher le glyphe correspondant.

21
Jonathan Gibbons

Je suis d'accord avec @zwol sur le fait qu'il s'agit d'une erreur de conception. mais je suis encore plus critique à ce sujet.

\u escape est utile dans les littéraux de chaîne et char. et c'est le seul endroit où il devrait exister. Il devrait être traité de la même manière que d'autres échappements comme \n; et "\u000A"devrait signifie exactement "\n".

Il est absolument inutile d’avoir \uxxxx dans les commentaires - personne ne peut le lire.

De même, il est inutile d'utiliser \uxxxx dans une autre partie du programme. La seule exception concerne probablement les API publiques contraintes de contenir des caractères non ascii - quelle est la dernière fois que nous avons vu cela?

Les concepteurs avaient leurs raisons en 1995, mais 20 ans plus tard, cela semblait être un mauvais choix.

(question aux lecteurs - pourquoi cette question continue-t-elle à obtenir de nouveaux votes? Cette question est-elle liée à un endroit populaire?)

20
ZhongYu

Les seules personnes qui peuvent expliquer pourquoi les échappements Unicode ont été implémentées sont celles qui ont écrit la spécification.

Une raison plausible à cela est qu'il y avait le désir d'autoriser l'ensemble du BMP en tant que caractères possibles du code source Java. Cela pose cependant un problème:

  • Vous voulez pouvoir utiliser n'importe quel caractère BMP.
  • Vous voulez pouvoir entrer n'importe quel caractère BMP raisonnablement facile. Une solution consiste à échapper à Unicode.
  • Vous voulez que les spécifications lexicales soient faciles à lire et à écrire pour les humains, et relativement faciles à mettre en œuvre.

C'est incroyablement difficile lorsque des échappées Unicode entrent en conflit: cela crée toute une charge de nouvelles règles lexer.

La solution de facilité consiste à analyser le texte en deux étapes: commencez par rechercher et remplacer toutes les échappées Unicode par le caractère qu'il représente, puis analysez le document obtenu comme si les échappées Unicode n'existaient pas.

L'avantage de ceci est qu'il est facile à spécifier, ce qui simplifie la spécification et sa mise en œuvre.

L’inconvénient, c’est bien votre exemple.

11
Martijn