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

Le code suivant produit la sortie "Hello World!&quot ; (non vraiment, essayez).

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 qu'un commentaire est "exécuté".

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

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

Solution

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

Comme indiqué dans [JLS Section 3.3] (https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.3), cela permet à tout outil basé sur l'ASCII de traiter les fichiers sources :

[...] 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 l'ASCII. [...]

Cela donne une garantie fondamentale pour l'indépendance de la plate-forme (indépendance des jeux de caractères supportés) 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 que cela puisse interférer avec la sémantique de manière aussi subtile n'est qu'un effet secondaire (malheureux).

Il existe de nombreux problèmes sur ce thème et [Java Puzzlers] (http://www.javapuzzlers.com/) de Joshua Bloch et Neal Gafter présente la variante suivante :

Est-ce 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 de l'énigme, ils soulignent ce qui suit :

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


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

Commentaires (21)

L'échappement \u000d termine un commentaire parce que les échappements \u sont uniformément convertis en caractères Unicode correspondants avant que le programme ne soit tokenisé. Vous pouvez également utiliser \u0057\u0057 à la place de // pour débuter un commentaire.

Il s'agit d'un bogue dans votre IDE, qui devrait mettre en évidence la syntaxe de la ligne pour montrer clairement que le \u000d termine le commentaire.

Il s'agit également d'une erreur de conception dans le langage. Elle ne peut pas être corrigée maintenant, car cela casserait les programmes qui en dépendent. Les échappements \u devraient soit être convertis en caractères Unicode correspondants par le compilateur uniquement dans les contextes où cela "a du sens" (littéraux de chaînes de caractères et identificateurs, et probablement nulle part ailleurs), soit il devrait leur être interdit de 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 terminé par l'échappement \u000d, sans interférer avec les cas où les échappements \u sont utiles - notez que cela inclut l'utilisation des échappements \u à l'intérieur des commentaires comme moyen d'encoder des commentaires dans un script non-latin, parce que l'éditeur de texte pourrait avoir une vision plus large des cas où les échappements \u sont significatifs que le compilateur. (Je ne connais pas d'éditeur ou d'IDE qui affichera les échappements \u comme les caractères correspondants dans tout contexte, cependant).

Il y a une erreur de conception similaire dans la famille C,1 où le backslash-newline est traité avant que les limites du commentaire ne soient déterminées, donc par ex.

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

J'en parle pour illustrer le fait qu'il est facile de faire cette erreur de conception particulière, et de ne pas s'en rendre compte jusqu'à ce qu'il soit trop tard pour la corriger, si vous avez l'habitude de penser à la tokenisation et à l'analyse syntaxique de la manière dont les programmeurs de compilateurs y pensent. En fait, si vous avez déjà défini votre grammaire formelle et que quelqu'un propose un cas syntaxique spécial (trigraphes, backslash-newline, encodage de caractères Unicode arbitraires dans des fichiers sources limités à l'ASCII, etc.) qui doit être intégré, il est plus facile d'ajouter une passe de transformation avant le tokenizer que de redéfinir le tokenizer pour faire attention aux endroits où il est logique d'utiliser ce cas spécial.

1 Pour les pédants : Je suis conscient que cet aspect du C était 100% intentionnel, avec le raisonnement &mdash ; je n'invente rien &mdash ; que cela vous permettrait de forcer mécaniquement du code avec des lignes arbitrairement longues sur des cartes perforées. C'était quand même une décision de conception incorrecte.

Commentaires (12)

Je suis d'accord avec @zwol pour dire que c'est une erreur de conception, mais je suis encore plus critique à son égard.

L'échappement \u est utile dans les chaînes et les caractères littéraux ; et c'est le seul endroit où il devrait exister. Il devrait être géré de la même manière que les autres échappements comme `n' ; et "\u000A" devrait signifier exactement "\n".

Il n'y a absolument aucun intérêt à avoir \uxxxx dans les commentaires - personne ne peut le lire.

De même, il n'y a aucun intérêt à utiliser `uxxxx' dans d'autres parties du programme. La seule exception est probablement dans les API publiques qui sont forcées de contenir des caractères non ASCII - à quand remonte la dernière fois que nous avons vu cela ?

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

*(Question aux lecteurs - pourquoi cette question reçoit-elle sans cesse de nouveaux votes ? Cette question est-elle liée à un site populaire ?)

Commentaires (18)