Pourquoi ce code, écrit à l'envers, imprime "Bonjour tout le monde!"

261

Voici un code que j'ai trouvé sur Internet:

class M‮{public static void main(String[]a‭){System.out.print(new char[]
{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}    

Ce code s'imprime Hello World!sur l'écran; vous pouvez le voir fonctionner ici . Je peux voir clairement public static void mainécrit, mais c'est à l'envers. Comment fonctionne ce code? Comment cela compile-t-il même?

Edit: J'ai essayé ce code dans IntellIJ, et cela fonctionne très bien. Cependant, pour une raison quelconque, cela ne fonctionne pas dans notepad ++, avec cmd. Je n'ai toujours pas trouvé de solution à cela, donc si quelqu'un le fait, commentez ci-dessous.

Citrouille imaginaire
la source
38
Celui-ci est drôle ... Quelque chose à voir avec le support RTL?
Eugene Sh.
12
Il y a le caractère Unicode # 8237; juste après Met aussi après []a: fileformat.info/info/unicode/char/202d/index.htm Ça s'appelle LEFT-TO-RIGHT OVERRIDE
Riiverside
45
xkcd obligatoire: xkcd.com/1137
Pac0
4
Vous pouvez très facilement voir ce qui se passe ici simplement en effectuant des sélections dans l'extrait de code à l'aide de votre souris.
Andreas Rejbrand
14
niam diov citats cilbupsonne comme un proverbe latin ..
Mick Mnemonic

Réponses:

250

Il y a des caractères invisibles ici qui modifient la façon dont le code est affiché. Dans Intellij ceux-ci peuvent être trouvés en copiant-collant le code dans une chaîne vide ( ""), qui les remplace par des échappements Unicode, supprimant leurs effets et révélant l'ordre que le compilateur voit.

Voici la sortie de ce copier-coller:

"class M\u202E{public static void main(String[]a\u202D){System.out.print(new char[]\n"+
        "{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}   "

Les caractères du code source sont stockés dans cet ordre et le compilateur les traite comme étant dans cet ordre, mais ils s'affichent différemment.

Notez le \u202Ecaractère, qui est un remplacement de droite à gauche, en commençant un bloc où tous les caractères sont forcés d'être affichés de droite à gauche, et le \u202D, qui est un remplacement de gauche à droite, en commençant un bloc imbriqué où tous les caractères sont forcés dans un ordre de gauche à droite, remplaçant le premier remplacement.

Ergo, lorsqu'il affiche le code d'origine, class Ms'affiche normalement, mais l' \u202Einverse l'ordre d'affichage de tout de là vers le \u202D, qui inverse à nouveau tout. (Formellement, tout depuis le \u202Dterminateur de ligne est inversé deux fois, une fois en raison de la \u202Det une fois avec le reste du texte inversé en raison de la \u202E, c'est pourquoi ce texte apparaît au milieu de la ligne au lieu de la fin.) La directionnalité de la ligne suivante est gérée indépendamment de la première en raison du terminateur de ligne, elle {'H','e','l','l','o',' ','W','o','r','l','d','!'});}}est donc affichée normalement.

Pour l'algorithme bidirectionnel Unicode complet (extrêmement complexe, des dizaines de pages de long), voir l' Annexe # 9 de la norme Unicode .

Davis Broda
la source
Vous n'expliquez pas ce que le compilateur (par opposition à la routine d'affichage) fait avec ces caractères Unicode eux-mêmes. Je pourrais les ignorer carrément (ou les traiter comme des espaces blancs), ou il pourrait les interpréter comme contribuant réellement au code source. Je ne connais pas les règles Java ici, mais le fait qu'elles soient placées à la fin des identifiants autrement inutilisés me suggère que ce pourrait être ce dernier, et les caractères Unicode font en fait partie de ces noms d'identifiants.
Marc van Leeuwen
Est-ce que cela fonctionnerait de la même manière en c #, par intérêt?
IanF1
14
@ IanF1 Cela fonctionnerait dans n'importe quelle langue où le compilateur / interprète compte les caractères RTL et LTR comme des espaces. Mais ne faites jamais cela dans le code de production si vous appréciez la raison de la prochaine personne à toucher votre code, qui pourrait bien être vous.
wizzwizz4
2
Ou, en d'autres termes: "Codez toujours comme si la personne qui finit par maintenir votre code est un psychopathe violent qui sait où vous vivez." , @ IanF1. Ou peut-être: "Codez toujours comme si la personne qui finit par maintenir votre code vous nommerait et vous ferait honte en tant qu'auteur d'origine sur Stack Overflow."
Cody Gray
43

Il semble différent en raison de l' algorithme bidirectionnel Unicode . Il y a deux caractères invisibles de RLO et LRO que l'algorithme bidirectionnel Unicode utilise pour changer l' apparence visuelle des caractères imbriqués entre ces deux métacaractères.

Le résultat est que visuellement ils regardent dans l'ordre inverse, mais les caractères réels en mémoire ne sont pas inversés. Vous pouvez analyser les résultats ici . Le compilateur Java ignorera RLO et LRO et les traitera comme des espaces, c'est pourquoi le code se compile.

Remarque 1: Cet algorithme est utilisé par les éditeurs de texte et les navigateurs pour afficher visuellement les caractères à la fois les caractères LTR (anglais) et RTL (par exemple l'arabe, l'hébreu) ​​en même temps - d'où le sens "bi". Vous pouvez en savoir plus sur l'algorithme bidirectionnel sur le site Web d'Unicode .
Remarque 2: Le comportement exact de LRO et RLO est défini dans la section 2.2 de l'algorithme.

James Lawson
la source
Quel est le but d'une telle capacité?
Eugene Sh.
6
Ces caractères sont parfois nécessaires pour rendre visuellement correctement l'arabe et l'hébreu. Ces langues sont lues et écrites de droite à gauche (RTL), le premier caractère lu / écrit apparaît sur le côté droit . Vous pouvez en lire plus ici .
James Lawson
Les caractères arabes et hébreux sont intrinsèquement RTL, cependant - ils apparaîtront RTL même sans remplacement explicite, et ils inverseront même automatiquement l'ordre de certains autres caractères à proximité, je pense principalement la ponctuation - des remplacements explicites sont donc rarement nécessaires.
user2357112 prend en charge Monica
Cette page décrit ici quand les remplacements sont nécessaires. @ user2357112 a raison, ils sont rarement nécessaires. En effet, lorsque vous avez des signes de ponctuation, des guillemets et des chiffres - ces caractères spéciaux sont considérés comme "neutres". Pour un ordinateur qui ne peut pas lire les mots et comprendre le contexte, il n'est pas clair s'il faut les traiter comme LTR ou RTL, mais l'algorithme bidi doit choisir un ordre. Parfois, il "se trompe" et vous devez utiliser ces caractères de remplacement pour le "corriger".
James Lawson
3
De plus, U + 202E et U + 202D ne sont pas considérés comme des espaces blancs. Java considère uniquement l'espace ASCII, la tabulation horizontale, le flux de formulaire et CR / LF / CRLF comme des espaces . Ils font en fait lexiquement partie des identifiants M\u202Eet a\u202D, mais ces identifiants semblent être traités comme équivalents à Met a. (Le JLS ne fait pas un bon travail pour expliquer cela.)
user2357112 prend en charge Monica
28

Le personnage U+202Ereflète le code de droite à gauche, il est cependant très intelligent. Est caché à partir du M,

"class M\u202E{..."

Comment ai-je trouvé la magie derrière tout ça?

Eh bien, au début, quand j'ai vu la question que je tenais, "c'est une sorte de blague, perdre du temps à quelqu'un d'autre", mais ensuite, j'ai ouvert mon IDE ("IntelliJ"), créé une classe et passé le code ... et il a compilé !!! Alors, j'ai jeté un coup d'œil et j'ai vu que le "vide statique public" était en arrière, alors je suis allé là-bas avec le curseur, et j'ai effacé quelques caractères ... Et que se passe-t-il? Les caractères ont commencé à s'effacer vers l'arrière , donc, je pensais que mmm .... rare ... Je dois l'exécuter ... Alors je continue à exécuter le programme, mais d'abord je devais l' enregistrer ... et c'est à ce moment que j'ai je l'ai trouvé! . Je n'ai pas pu enregistrer le fichier parce que mon IDE a dit qu'il y avait un codage différent pour certains caractères, et pointez-moi où était-il, Donc je commence une recherche dans Google pour les caractères spéciaux qui pourraient faire le travail, et c'est tout :)

Un peu

l'algorithme bidirectionnel Unicode, et U+202Eimpliqué, expliquer brièvement :

Le standard Unicode prescrit un ordre de représentation de la mémoire appelé ordre logique. Lorsque le texte est présenté en lignes horizontales, la plupart des scripts affichent des caractères de gauche à droite. Cependant, il existe plusieurs scripts (tels que l'arabe ou l'hébreu) ​​où l'ordre naturel du texte horizontal affiché est de droite à gauche. Si tout le texte a une direction horizontale uniforme, alors l'ordre du texte affiché est sans ambiguïté.

Cependant, comme ces scripts de droite à gauche utilisent des chiffres écrits de gauche à droite, le texte est en fait bidirectionnel: un mélange de texte de droite à gauche et de gauche à droite. En plus des chiffres, des mots intégrés de l'anglais et d'autres scripts sont également écrits de gauche à droite, produisant également du texte bidirectionnel. Sans spécification claire, des ambiguïtés peuvent apparaître lors de la détermination de l'ordre des caractères affichés lorsque la direction horizontale du texte n'est pas uniforme.

Cette annexe décrit l'algorithme utilisé pour déterminer la directionnalité du texte Unicode bidirectionnel. L'algorithme étend le modèle implicite actuellement utilisé par un certain nombre d'implémentations existantes et ajoute des caractères de formatage explicites pour des circonstances spéciales. Dans la plupart des cas, il n'est pas nécessaire d'inclure des informations supplémentaires avec le texte pour obtenir un ordre d'affichage correct.

Cependant, dans le cas d'un texte bidirectionnel, il existe des circonstances où un ordre bidirectionnel implicite n'est pas suffisant pour produire un texte compréhensible. Pour traiter ces cas, un ensemble minimal de caractères de mise en forme directionnelle est défini pour contrôler l'ordre des caractères lors du rendu. Cela permet un contrôle exact de l'ordre d'affichage pour un échange lisible et garantit que le texte brut utilisé pour des éléments simples comme les noms de fichiers ou les étiquettes peut toujours être correctement commandé pour l'affichage.

Pourquoi créer un algorithme comme celui-ci ?

l'algorithme bidi peut rendre une séquence de caractères arabes ou hébreux les uns après les autres de droite à gauche.

Damián Rafael Lattenero
la source
4

Le chapitre 3 de la spécification du langage fournit une explication en décrivant en détail comment la traduction lexicale est effectuée pour un programme Java. Ce qui compte le plus pour la question:

Les programmes sont écrits en Unicode (§3.1) , mais des traductions lexicales sont fournies (§3.2) afin que les échappements Unicode (§3.3) puissent être utilisés pour inclure tout caractère Unicode utilisant uniquement des caractères ASCII.

Un programme est donc écrit en caractères Unicode, et l'auteur peut y échapper en utilisant \uxxxxdans le cas où l'encodage de fichier ne prend pas en charge le caractère Unicode, auquel cas il est traduit au caractère approprié. L'un des caractères Unicode présents dans ce cas est \u202E. Il n'est pas affiché visuellement dans l'extrait de code, mais si vous essayez de changer l'encodage du navigateur, les caractères masqués peuvent apparaître.

Par conséquent, la traduction lexicale se traduit par la déclaration de classe:

class M\u202E{

ce qui signifie que l'identifiant de classe est M\u202E. La spécification considère cela comme un identifiant valide:

Identifier:
    IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
    JavaLetter {JavaLetterOrDigit}

Une "lettre ou chiffre Java" est un caractère pour lequel la méthode Character.isJavaIdentifierPart(int)renvoie vrai.

M Anouti
la source
Désolé mais c'est en arrière (jeu de mots voulu). Il n'y a pas d'échappement dans le code source; vous décrivez comment cela aurait pu être écrit. Et, il compile dans une classe nommée "M" (un seul caractère).
Tom Blodget
@TomBlodget En effet, mais le point (que j'ai en fait souligné dans la citation de spécification) est que le compilateur peut également traiter des caractères Unicode bruts. C'est vraiment toute l'explication. La traduction d'échappement n'est qu'une information supplémentaire et n'est pas directement liée à ce cas. Quant à la classe compilée, je pense que c'est parce que le caractère du commutateur RTL est en quelque sorte rejeté par le compilateur. Je vais essayer de voir si cela est prévu, mais je pense que cela se produit après la phase de traduction lexicale.
M Anouti