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

1356

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 \u000dcomme 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!");
}

De ce fait, un commentaire est "exécuté".

Puisque cela peut être utilisé pour "cacher" du code malveillant ou tout ce qu'un mauvais programmeur peut concevoir, pourquoi est-il autorisé dans les commentaires ?

Pourquoi cela est-il autorisé par la spécification Java?

Reg
la source
44
"Pourquoi est-ce autorisé?" Me semble trop basé sur l'opinion. Les concepteurs de langage ont pris une décision, que faut-il savoir d'autre? À moins que vous ne trouviez une déclaration de la personne qui prend cette décision, nous ne pouvons que spéculer.
Ingo Bürk
194
Une chose intéressante est au moins que l'IDE d'OP se trompe évidemment et affiche une mise en surbrillance incorrecte,
dhke
14
Peut-être lié: stackoverflow.com/questions/4448180/…
dhke
47
@Tobb Mais les concepteurs Java visitent SO donc il est possible d'obtenir des réponses de l'un d'eux. Il peut également exister des ressources qui répondent déjà à cette question.
Pshemo
41
La réponse simple est que le code n'est pas du tout dans un commentaire, selon les règles de la langue, donc la question est mal formée.
Marquis de Lorne

Réponses:

741

Le décodage Unicode a lieu avant toute autre traduction lexicale. Le principal avantage de ceci est qu'il est trivial de faire des allers-retours entre ASCII et tout autre encodage. Vous n'avez même pas besoin de savoir où commencent et finissent les commentaires!

Comme indiqué dans la section 3.3 de JLS, cela permet à tout outil basé sur ASCII de traiter les fichiers source:

[...] Le langage de programmation Java spécifie une manière standard de transformer un programme écrit en Unicode en ASCII qui change un programme en une forme qui peut être traitée par des outils basés sur 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é pour la plate-forme Java.

La possibilité 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 du code dans des langues non latines. Le fait qu'il puisse interférer avec la sémantique de manière si subtile n'est qu'un effet secondaire (malheureux).

Il existe de nombreux pièges sur ce thème et Java Puzzlers de Joshua Bloch et Neal Gafter comprenait la variante suivante:

S'agit-il d'un programme Java légal? Si oui, qu'imprime-t-il?

\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 un simple programme "Hello World".)

Dans la solution au casse-tête, ils soulignent ce qui suit:

Plus sérieusement, ce puzzle sert à renforcer les leçons des trois précédents: les échappements Unicode sont essentiels lorsque vous devez insérer des caractères qui ne peuvent être représentés d'aucune autre manière dans votre programme. Évitez-les dans tous les autres cas.


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

aioobe
la source
84
Bref alors, Java le permet intentionnellement: le "bug" est dans l'IDE de l'OP?
Bathsheba
60
@Bathsheba: C'est plus dans la tête des gens. Les gens n'essaient pas de comprendre comment fonctionne l'analyse Java, donc les IDE affichent parfois le code de manière incorrecte. Dans l'exemple ci-dessus, le commentaire doit se terminer par \u000det la partie qui suit doit avoir des reflets de code.
Aaron Digulla
62
Une autre erreur courante consiste à coller des chemins Windows dans le code, // C:\user\...ce qui entraîne une erreur de compilation car il \userne s'agit pas d'une séquence d'échappement Unicode valide.
Aaron Digulla
50
Dans éclipse, le code après \u000dest partiellement mis en évidence. Après avoir appuyé sur Ctrl + Maj + F, le caractère est remplacé par une nouvelle ligne et le reste de la ligne est
encapsulé
20
@TheLostMind Si je comprends bien la réponse, vous devriez également pouvoir reproduire cela avec des commentaires de bloc. \u002A/devrait terminer le commentaire.
Taemyr
141

Puisque cela n'a pas encore été résolu, voici une explication, pourquoi la traduction des échappements Unicode se produit avant tout autre traitement de code source:

L'idée derrière cela était qu'il permet des traductions sans perte de code source Java entre différents encodages de caractères. Aujourd'hui, il existe un support Unicode répandu, et cela ne semble pas poser de 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, apportez quelques modifications ( y compris la compilation et le test) et l'envoi du résultat, le tout sans endommager quelque chose.

Ainsi, le code source Java peut être écrit dans n'importe quel encodage et permet une large gamme de caractères dans les identifiants, les caractères et les Stringlittéraux et les commentaires. Ensuite, afin de le transférer sans perte, tous les caractères non pris en charge par l'encodage cible sont remplacés par leurs échappements Unicode.

Il s'agit d'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 car la traduction de leurs caractères Unicode réels à l'intérieur du compilateur s'effectue également indépendamment de la syntaxe du code source Java. Cela implique que vous pouvez 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 fonctionnalité étrange qui n'a même pas mentionné: la \uuuuuuxxxxsyntaxe:

Lorsqu'un outil de traduction échappe des caractères et rencontre une séquence qui est déjà une séquence échappée, il doit insérer un élément supplémentaire udans la séquence, en le convertissant \ucafeen \uucafe. La signification ne change pas, mais lors de la conversion dans l'autre sens, l'outil doit simplement en supprimer une uet remplacer uniquement les séquences contenant une seule upar leurs caractères Unicode. De cette façon, même les échappements Unicode sont conservés dans leur forme d'origine lors de la conversion dans les deux sens. Je suppose que personne n'a jamais utilisé cette fonctionnalité…

Holger
la source
1
Fait intéressant, native2asciine semble pas utiliser la \uu...xxxxsyntaxe,
ninjalj
5
Oui, native2asciiétait destiné à aider à la préparation des bundles de ressources en les convertissant en iso-latin-1 comme il a Properties.loadété corrigé pour lire latin-1 uniquement. Et là, les règles sont différentes, pas de \uuu…syntaxe et pas de stade de traitement précoce. Dans les fichiers de propriétés, property=multi\u000alineest en effet le même que property=multi\nline. (En contradiction avec l'expression «à l'aide des échappements Unicode tels que définis dans la section 3.3 de la spécification du langage Java ™» de la documentation)
Holger
10
Notez que cet objectif de conception aurait pu être atteint sans aucune des verrues; la manière la plus simple aurait été d'interdire les \uéchappements pour générer des caractères dans la plage U + 0000–007F. (Tous ces caractères peuvent être représentés nativement par tous les encodages nationaux qui étaient pertinents dans les années 1990 - enfin, peut-être à l'exception de certains des caractères de contrôle, mais vous n'en avez pas besoin de toute façon pour écrire Java.)
zwol
3
@zwol: eh bien, si vous excluez de toute façon les caractères de contrôle qui ne sont pas autorisés dans le code source Java, vous avez raison. Néanmoins, cela impliquerait de compliquer les règles. Et aujourd'hui, il est trop tard pour discuter de la décision…
Holger
ah le problème de sauvegarder un document en utf8 et non latin ou autre chose. Toutes mes bases de données ont également été cassées à cause de ce non
David 天宇 Wong
106

Je vais ajouter complètement inefficacement le point, juste parce que je ne peux pas m'en empêcher et que je ne l'ai pas encore vu, que la question n'est pas valide car elle contient une prémisse cachée qui est erronée, à savoir que le code est en un commentaire!

En Java, le code source \ u000d est à tous égards é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, à quoi correspond exactement cette séquence de caractères syntaxiquement:

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

À mon humble avis, 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. "Exécuter du code dans les commentaires" n'est pas autorisé en Java, comme vous pouvez vous y attendre.

Une grande partie de la confusion provient du fait que les surligneurs de syntaxe et les IDE ne sont pas suffisamment 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 plutôt qu'avant, comme c'est le javaccas.

Pepijn Schmitz
la source
6
Je suis d'accord, ce n'est pas une "erreur de conception" java, mais c'est un bug IDE.
bvdb
3
La question est plutôt de savoir pourquoi un code qui ressemble à un commentaire à quelqu'un qui n'est pas familier avec cet aspect particulier du langage et peut-être sans référence à la coloration syntaxique, n'est en fait pas un commentaire. S'opposer au fait que la question n'est pas valable n'est pas sincère.
Phil
@Phil: il ne ressemble à un commentaire que lorsqu'il est visualisé avec des outils particuliers, d'autres le montrent autrement.
jmoreno
1
@jmoreno, il ne faut pas avoir autre chose qu'un éditeur de texte pour lire le code. À tout le moins, il viole le principe de la moindre surprise, à savoir que les commentaires de style // continuent jusqu'au caractère \ n suivant - pas à toute autre séquence qui est finalement remplacée par \ n finalement. On ne s'attend jamais à ce que les commentaires soient autres que dépouillés. Mauvais préprocesseur.
Phil
69

L' \u000dévasion termine un commentaire car les \uéchappements sont uniformément convertis en caractères Unicode correspondants avant que le programme ne soit tokenisé. Vous pouvez également utiliser \u0057\u0057au lieu de //pour commencer un commentaire.

Il s'agit d'un bogue dans votre IDE, qui devrait mettre en surbrillance la ligne pour indiquer clairement que \u000dle commentaire se termine.

Il s'agit également d'une erreur de conception dans la langue. Cela ne peut pas être corrigé maintenant, car cela casserait les programmes qui en dépendent. \ules échappements doivent être convertis en caractère Unicode correspondant par le compilateur uniquement dans des contextes où cela "fait sens" (littéraux de chaîne et identificateurs, et probablement nulle part ailleurs) ou ils doivent avoir été interdits pour générer des caractères dans la plage U + 0000–007F , ou les deux. L'une ou l'autre de ces sémantiques aurait empêché le commentaire d'être interrompu par l' \u000dévasion, sans interférer avec les cas où les \uéchappements sont utiles - notez que cela inclut l' utilisation des \uéchappements dans les commentaires comme moyen d'encoder les commentaires dans un script non latin, car le éditeur de texte pourrait avoir une vision plus large de l'endroit où\ules échappements sont significatifs par rapport au compilateur. ( Cependant, je ne connais aucun éditeur ou IDE qui affichera les \uéchappements en tant que caractères correspondants dans n'importe quel contexte.)

Il existe une erreur de conception similaire dans la famille C, 1 où la barre oblique inversée est traitée avant que les limites des commentaires ne soient déterminées, par exemple

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

J'apporte cela pour illustrer qu'il se trouve qu'il est facile de faire cette erreur de conception particulière, et de ne pas réaliser que c'est une erreur jusqu'à ce qu'il soit trop tard pour la corriger, si vous avez l'habitude de penser à la tokenisation et à l'analyse de la façon dont les programmeurs du compilateur pensent sur la tokenisation et l'analyse. Fondamentalement, si vous avez déjà défini votre grammaire formelle et que quelqu'un propose un cas spécial syntaxique - trigraphes, barre oblique inversée, encodage de caractères Unicode arbitraires dans des fichiers source limités en ASCII, peu importe - qui doit être coincé, il est plus facile de ajouter un passage de transformation avant le tokenizer que de redéfinir le tokenizer pour faire attention à l'endroit où il est judicieux d'utiliser ce cas spécial.

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

zwol
la source
17
Je n'irais pas jusqu'à dire que c'est une erreur de conception . Je pourrais convenir avec vous que c'était un mauvais choix de conception, ou un choix aux conséquences malheureuses, mais je pense toujours que cela fonctionne comme prévu par les concepteurs du langage: il vous permet d'utiliser n'importe quel caractère unicode n'importe où dans le fichier, tout en conservant le codage ASCII du fichier.
aioobe
12
Cela dit, je pense que le choix de l'étape de traitement pour \uétait moins absurde que la décision de suivre l'exemple de C en utilisant des zéros de tête pour la notation octale. Bien que la notation octale soit parfois utile, je n'ai encore entendu personne expliquer pourquoi un zéro non significatif est un bon moyen de l'indiquer.
supercat
3
@supercat Les personnes qui ont lancé cette fonctionnalité dans C89 généralisaient le comportement du préprocesseur K&R d'origine plutôt que de concevoir une fonctionnalité à partir de zéro. Je doute qu'ils connaissent les meilleures pratiques des cartes perforées, et je doute également que la fonctionnalité ait jamais été utilisée pour son objectif déclaré, sauf peut-être pour un ou deux exercices de recalcul.
zwol
8
@supercat Je n'aurais pas de problème avec Java \ucomme transformation de pré-tokenisation s'il était interdit de produire des caractères dans la plage U + 0000..U + 007F. C'est la combinaison de "cela fonctionne partout" et de "cela alias des caractères ASCII avec une signification syntaxique" qui le rétrograde de maladroit à complètement faux.
zwol
4
Sur votre "pour les pédants": Bien sûr, à cette époque, le //commentaire sur une seule ligne n'existait pas . Et puisque C a un terminateur de déclaration qui n'est pas une nouvelle ligne, il serait principalement utilisé pour les chaînes longues, sauf que pour autant que je puisse déterminer la "concaténation littérale de chaîne" était là de K&R.
Mark Hurd
22

Il s'agissait d'un choix de conception intentionnel qui remonte à la conception originale de Java.

À ceux qui demandent "qui veut échapper à Unicode dans les commentaires?", Je suppose 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 gens puissent utiliser des caractères Unicode arbitraires partout où cela est légal dans un programme Java, le plus souvent dans les commentaires et les chaînes.

Il s'agit sans doute d'une lacune dans les programmes (comme les IDE) utilisés pour afficher le texte source que ces programmes ne peuvent pas interpréter les échappements Unicode et afficher le glyphe correspondant.

Jonathan Gibbons
la source
8
De nos jours, nous utilisons UTF-8 pour notre code source, et pouvons utiliser directement les caractères Unicode, pas besoin d'échappements.
Paŭlo Ebermann
21

Je suis d'accord avec @zwol qu'il s'agit d'une erreur de conception; mais j'en suis encore plus critique.

\uescape est utile dans les littéraux chaîne et char; et c'est le seul endroit où cela devrait exister. Il doit être traité de la même manière que les autres évasions comme \n; et "\u000A" devrait signifier exactement "\n".

Il est absolument inutile d'avoir des \uxxxxcommentaires - personne ne peut le lire.

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

Les concepteurs ont eu leurs raisons en 1995, mais 20 ans plus tard, cela semble être un mauvais choix.

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

ZhongYu
la source
5
Je suppose que vous ne traînez pas, où des caractères non ASCII sont utilisés dans les API. Il y a des gens qui l'utilisent (pas moi), par exemple dans les pays asiatiques. Et lorsque vous utilisez des caractères non ASCII dans les identificateurs, leur interdiction dans les commentaires de documentation n'a pas de sens. Néanmoins, les autoriser à l'intérieur d'un jeton et leur permettre de changer la signification ou la limite d'un jeton sont des choses différentes.
Holger
15
ils peuvent utiliser un encodage de fichier approprié. pourquoi écrire int \u5431quand vous pouvez le faireint 整
ZhongYu
3
Que ferez-vous lorsque vous devrez compiler du code par rapport à leur API et ne pourrez pas utiliser l'encodage approprié (supposez qu'il n'y avait pas de UTF-8prise en charge généralisée en 1995). Vous n'avez qu'à appeler une méthode et vous ne voulez pas installer le pack de prise en charge des langues asiatiques de votre système d'exploitation (rappelez-vous, les années 90) pour cette seule méthode…
Holger
5
Ce qui est beaucoup plus clair maintenant qu'en 1995, c'est que vous devez mieux connaître l'anglais si vous voulez programmer. La programmation est une interaction internationale et presque toutes les ressources sont en anglais.
ZhongYu
8
Je ne pense pas que cela ait changé. La documentation de Java était également en anglais la plupart du temps. Il y avait une traduction japonaise maintenue pendant un certain temps, mais le maintien de deux langues ne soutient pas vraiment l'idée de la maintenir pour tous les endroits du monde (elle l'a plutôt réfutée). Et avant cela, il n'y avait de toute façon pas de langage courant avec le support Unicode dans les identifiants. Donc je suppose que quelqu'un pensait que le code source localisé était la prochaine grande chose. Je dirais heureusement que cela n'a pas décollé.
Holger
11

Les seules personnes qui peuvent expliquer pourquoi les échappements Unicode ont été implémentés tels quels sont les personnes 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 saisir n'importe quel caractère BMP assez facilement. Un moyen de le faire est d'utiliser les échappements Unicode.
  • Vous voulez que la spécification lexicale soit facile à lire et à écrire pour les humains, et relativement facile à mettre en œuvre également.

C'est incroyablement difficile lorsque les échappements Unicode entrent dans la mêlée: cela crée toute une série de nouvelles règles de lexer.

La solution la plus simple consiste à effectuer une lexing en deux étapes: recherchez et remplacez d'abord tous les échappements Unicode par le caractère qu'il représente, puis analysez le document résultant comme si les échappements Unicode n'existaient pas.

L'avantage est qu'il est facile à spécifier, il simplifie donc la spécification et est facile à mettre en œuvre.

L'inconvénient est bien votre exemple.

Martijn
la source
2
Ou, limitez l'utilisation de \ uxxxx aux identificateurs, littéraux de chaîne et constantes de caractères. C'est ce que fait C11.
ninjalj
cela complique vraiment les règles de l'analyseur, car c'est ce qui définit ces choses, et c'est ce que je spécule, c'est en partie la raison pour laquelle c'est comme ça.
Martijn