Warum ist das Ausführen von Java-Code in Kommentaren mit bestimmten Unicode-Zeichen zulässig?


1356

Der folgende Code erzeugt die Ausgabe "Hello World!" (Nein wirklich, versuchen Sie es).

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

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

Der Grund dafür ist, dass der Java-Compiler das Unicode-Zeichen \u000dals neue Zeile analysiert und in Folgendes umwandelt:

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

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

Dies führt dazu, dass ein Kommentar "ausgeführt" wird.

Da dies verwendet werden kann, um bösartigen Code oder was auch immer ein böser Programmierer sich vorstellen kann, zu "verbergen", warum ist dies in Kommentaren erlaubt ?

Warum ist dies in der Java-Spezifikation zulässig?


44
"Warum ist das erlaubt?" Scheint mir zu meinungsbasiert zu sein. Die Sprachdesigner haben eine Entscheidung getroffen, was muss man sonst noch wissen? Sofern Sie keine Aussage der Person finden, die diese Entscheidung trifft, können wir nur spekulieren.
Ingo Bürk

194
Eine interessante Sache ist zumindest, dass die IDE von OP es offensichtlich falsch macht und falsche Hervorhebungen anzeigt,
dhke

14
Möglicherweise verwandt: stackoverflow.com/questions/4448180/…
dhke

47
@Tobb Aber Java-Designer besuchen SO, so dass es möglich ist , Antworten von einem von ihnen zu erhalten. Es können auch Ressourcen vorhanden sein, die diese Frage bereits beantworten.
Pshemo

41
Die einfache Antwort ist, dass der Code nach den Regeln der Sprache überhaupt nicht in einem Kommentar enthalten ist, sodass die Frage falsch formuliert ist.
Marquis von Lorne

Antworten:


741

Die Unicode-Decodierung erfolgt vor jeder anderen lexikalischen Übersetzung. Der Hauptvorteil davon ist, dass es trivial ist, zwischen ASCII und jeder anderen Codierung hin und her zu wechseln. Sie müssen nicht einmal herausfinden, wo Kommentare beginnen und enden!

Wie in JLS Abschnitt 3.3 angegeben, kann jedes ASCII-basierte Tool die Quelldateien verarbeiten:

[...] Die Programmiersprache Java gibt eine Standardmethode zum Umwandeln eines in Unicode geschriebenen Programms in ASCII an, mit der ein Programm in eine Form geändert wird, die von ASCII-basierten Tools verarbeitet werden kann. [...]

Dies bietet eine grundlegende Garantie für die Plattformunabhängigkeit (Unabhängigkeit von unterstützten Zeichensätzen), die für die Java-Plattform immer ein zentrales Ziel war.

Die Möglichkeit, ein beliebiges Unicode-Zeichen an einer beliebigen Stelle in der Datei zu schreiben, ist eine nette Funktion und besonders wichtig in Kommentaren, wenn Code in nicht-lateinischen Sprachen dokumentiert wird. Die Tatsache, dass es die Semantik auf solch subtile Weise stören kann, ist nur ein (unglücklicher) Nebeneffekt.

Es gibt viele Fallstricke zu diesem Thema und Java Puzzlers von Joshua Bloch und Neal Gafter enthielten die folgende Variante:

Ist das ein legales Java-Programm? Wenn ja, was wird gedruckt?

\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

(Dieses Programm stellt sich als einfaches "Hello World" -Programm heraus.)

In der Lösung für das Rätsel weisen sie auf Folgendes hin:

Im Ernst, dieses Puzzle dient dazu, die Lektionen der vorherigen drei zu verstärken: Unicode-Escapezeichen sind unerlässlich, wenn Sie Zeichen einfügen müssen, die auf keine andere Weise in Ihr Programm dargestellt werden können. Vermeiden Sie sie in allen anderen Fällen.


Quelle: Java: Code in Kommentaren ausführen?!


84
Kurz gesagt, Java erlaubt es absichtlich: Der "Fehler" ist in der IDE des OP?
Bathseba

60
@Bathsheba: Es ist mehr in den Köpfen der Menschen. Die Leute versuchen nicht zu verstehen, wie Java-Parsing funktioniert, daher zeigen IDEs den Code manchmal falsch an. Im obigen Beispiel sollte der Kommentar mit \u000dund der Teil danach mit Code-Hervorhebungen enden .
Aaron Digulla

62
Ein weiterer häufiger Fehler ist das Einfügen von Windows-Pfaden in den Code, // C:\user\...was zu einem Kompilierungsfehler führt, da \useres sich nicht um eine gültige Unicode-Escape-Sequenz handelt.
Aaron Digulla

50
In Eclipse wird der Code nach \u000dteilweise hervorgehoben. Nach dem Drücken von Strg + Umschalt + F wird das Zeichen durch eine neue Zeile ersetzt und der Rest der Zeile wird umbrochen
bluelDe

20
@TheLostMind Wenn ich die Antwort richtig verstehe, sollten Sie dies auch mit Blockkommentaren reproduzieren können. \u002A/sollte den Kommentar beenden.
Taemyr

141

Da dies noch nicht angesprochen wurde, hier eine Erklärung, warum die Übersetzung von Unicode-Escapezeichen vor jeder anderen Quellcode-Verarbeitung erfolgt:

Die Idee dahinter war, dass es verlustfreie Übersetzungen von Java-Quellcode zwischen verschiedenen Zeichencodierungen ermöglicht. Heutzutage gibt es eine weit verbreitete Unicode-Unterstützung, und dies scheint kein Problem zu sein, aber damals war es für einen Entwickler aus einem westlichen Land nicht einfach, einen Quellcode von seinem asiatischen Kollegen mit asiatischen Zeichen zu erhalten. Nehmen Sie einige Änderungen vor ( einschließlich Kompilieren und Testen) und Zurücksenden des Ergebnisses, ohne etwas zu beschädigen.

Java-Quellcode kann also in jeder beliebigen Codierung geschrieben werden und ermöglicht eine Vielzahl von Zeichen innerhalb von Bezeichnern, Zeichen, StringLiteralen und Kommentaren. Um es verlustfrei zu übertragen, werden dann alle Zeichen, die von der Zielcodierung nicht unterstützt werden, durch ihre Unicode-Escapezeichen ersetzt.

Dies ist ein reversibler Prozess, und der interessante Punkt ist, dass die Übersetzung von einem Tool durchgeführt werden kann, das nichts über die Java-Quellcodesyntax wissen muss, da die Übersetzungsregel nicht davon abhängig ist. Dies funktioniert, da die Übersetzung in ihre tatsächlichen Unicode-Zeichen im Compiler auch unabhängig von der Java-Quellcodesyntax erfolgt. Dies bedeutet, dass Sie eine beliebige Anzahl von Übersetzungsschritten in beide Richtungen ausführen können, ohne jemals die Bedeutung des Quellcodes zu ändern.

Dies ist der Grund für eine weitere seltsame Funktion, die noch nicht einmal erwähnt wurde: die \uuuuuuxxxxSyntax:

Wenn ein Übersetzungs - Tool Zeichen und trifft auf eine Sequenz zu entkommen , die bereits eine entkam Sequenz, sollte es eine zusätzliche Einfügen uin die Sequenz, die Umwandlung \ucafezu \uucafe. Die Bedeutung ändert sich nicht, aber beim Konvertieren in die andere Richtung sollte das Tool nur eine entfernen uund nur Sequenzen, die eine einzelne enthalten, udurch ihre Unicode-Zeichen ersetzen . Auf diese Weise bleiben auch Unicode-Escapezeichen beim Hin- und Herkonvertieren in ihrer ursprünglichen Form erhalten. Ich denke, niemand hat diese Funktion jemals benutzt ...


1
Interessanterweise native2asciischeint die \uu...xxxxSyntax nicht zu verwenden ,
Ninjalj

5
Ja, native2asciisollte helfen, Ressourcenpakete vorzubereiten, indem sie in Iso-Latin-1 konvertiert wurden, da dies Properties.loadso festgelegt wurde, dass nur Latin-1 gelesen wird. Und dort sind die Regeln unterschiedlich, keine \uuu…Syntax und keine frühe Verarbeitungsphase. In Eigenschaftendateien property=multi\u000alineist in der Tat das gleiche wie property=multi\nline. (Im Widerspruch zum Satz „Verwenden von Unicode-Escapezeichen wie in Abschnitt 3.3 der Java ™ -Sprachspezifikation definiert“ der Dokumentation)
Holger

10
Beachten Sie, dass dieses Entwurfsziel ohne eine der Warzen hätte erreicht werden können. \uAm einfachsten wäre es gewesen, Escapezeichen zu verbieten , um Zeichen im Bereich U + 0000–007F zu generieren. (Alle diese Zeichen können nativ durch alle nationalen Kodierungen dargestellt werden, die in den 1990er Jahren relevant waren
naja

3
@zwol: Nun, wenn Sie Steuerzeichen ausschließen, die im Java-Quellcode sowieso nicht erlaubt sind, haben Sie Recht. Dies würde jedoch bedeuten, dass die Regeln komplizierter werden. Und heute ist es zu spät, um die Entscheidung zu besprechen ...
Holger

ah das Problem, ein Dokument in utf8 zu speichern und nicht in Latein oder etwas anderem. Alle meine Datenbanken waren auch wegen dieses westlichen Unsinns kaputt
David 天宇 Wong

106

Ich werde den Punkt völlig ineffektiv hinzufügen, nur weil ich mir nicht helfen kann und ich noch nicht gesehen habe, dass die Frage ungültig ist, da sie eine versteckte Prämisse enthält, die falsch ist, nämlich dass der Code drin ist ein Kommentar!

Im Java-Quellcode entspricht \ u000d in jeder Hinsicht einem ASCII-CR-Zeichen. Es ist ein Zeilenende, schlicht und einfach, wo immer es auftritt. Die Formatierung in der Frage ist irreführend. Diese Zeichenfolge entspricht tatsächlich syntaktisch:

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

IMHO ist die richtigste Antwort daher: Der Code wird ausgeführt, weil er nicht in einem Kommentar enthalten ist. Es ist in der nächsten Zeile. "Ausführen von Code in Kommentaren" ist in Java nicht zulässig, wie Sie es erwarten würden.

Ein Großteil der Verwirrung rührt von der Tatsache her, dass Syntax-Textmarker und IDEs nicht hoch genug sind, um diese Situation zu berücksichtigen. Entweder verarbeiten sie die Unicode-Escape-Zeichen überhaupt nicht oder sie tun dies, nachdem sie den Code analysiert haben, anstatt wie zuvor javac.


6
Ich stimme zu, dies ist kein Java "Designfehler", aber es ist ein IDE-Fehler.
Bvdb

3
Die Frage ist eher, warum Code, der für jemanden, der mit diesem bestimmten Aspekt der Sprache nicht vertraut ist und möglicherweise nicht auf die Syntaxhervorhebung Bezug nimmt, wie ein Kommentar aussieht , tatsächlich kein Kommentar ist. Einspruch unter der Voraussetzung, dass die Frage ungültig ist, ist unaufrichtig.
Phil

@Phil: Es sieht nur wie ein Kommentar aus, wenn es mit bestimmten Tools angezeigt wird, andere zeigen es anders.
Jmoreno

1
@jmoreno man sollte nicht mehr als einen Texteditor haben müssen, um Code zu lesen. Zumindest verstößt es gegen das Prinzip der geringsten Überraschung, nämlich dass Kommentare im // Stil bis zum nächsten \ n Zeichen fortgesetzt werden - nicht zu einer anderen Sequenz, die letztendlich durch \ n ersetzt wird. Es wird nie erwartet, dass Kommentare etwas anderes als gestrippt sind. Schlechter Präprozessor.
Phil

69

Das \u000dEscape beendet einen Kommentar, da \uEscapezeichen vor dem Tokenisieren des Programms einheitlich in die entsprechenden Unicode-Zeichen konvertiert werden . Sie können auch \u0057\u0057anstelle von verwenden //, um einen Kommentar zu beginnen .

Dies ist ein Fehler in Ihrer IDE, der die Zeile syntaktisch hervorheben sollte, um zu verdeutlichen, dass \u000dder Kommentar endet.

Dies ist auch ein Designfehler in der Sprache. Es kann jetzt nicht korrigiert werden, da dies Programme beschädigen würde, die davon abhängen. \uEscapezeichen sollten vom Compiler entweder nur in Kontexten in das entsprechende Unicode-Zeichen konvertiert werden, in denen dies "sinnvoll" ist (Zeichenfolgenliterale und -bezeichner und wahrscheinlich nirgendwo anders), oder es sollte ihnen verboten sein, Zeichen im Bereich U + 0000–007F zu generieren , oder beides. Entweder diese Semantik hätte den Kommentar verhindert durch die beendet wird , \u000dFlucht, ohne sie mit den Fällen zu stören , wo \uentkommt nützlich Note sind , dass das beinhaltet die Verwendung von \uFluchten im Inneren Kommentar als eine Möglichkeit , zu kodieren Kommentaren in einer nicht-lateinischen Schrift, weil die Der Texteditor könnte einen breiteren Blick darauf werfen, wo\uEscapezeichen sind bedeutender als der Compiler. (Mir ist jedoch kein Editor oder keine IDE bekannt, die \uEscapezeichen in einem beliebigen Kontext als entsprechende Zeichen anzeigen .)

Es gibt einen ähnlichen Entwurfsfehler in der C-Familie 1, bei dem Backslash-Newline verarbeitet wird, bevor Kommentargrenzen bestimmt werden, z

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

Ich erwähne dies, um zu veranschaulichen, dass es einfach ist, diesen bestimmten Entwurfsfehler zu machen, und erst dann zu erkennen, dass es sich um einen Fehler handelt, wenn es zu spät ist, ihn zu korrigieren, wenn Sie es gewohnt sind, über Tokenisierung nachzudenken und die Denkweise von Compiler-Programmierern zu analysieren über Tokenisierung und Analyse. Wenn Sie Ihre formale Grammatik bereits definiert haben und dann jemand einen syntaktischen Sonderfall entwickelt - Trigraphen, Backslash-Newline, Codierung beliebiger Unicode-Zeichen in Quelldateien, die auf ASCII beschränkt sind, was auch immer -, die eingeklemmt werden müssen, ist dies einfacher Fügen Sie vor dem Tokenizer einen Transformationsdurchlauf hinzu, um den Tokenizer neu zu definieren und darauf zu achten, wo es sinnvoll ist, diesen Sonderfall zu verwenden.

1 Für Pedanten: Mir ist bewusst, dass dieser Aspekt von C zu 100% beabsichtigt war, mit der Begründung - ich denke mir das nicht aus -, dass Sie damit Code mit beliebig langen Zeilen mechanisch auf Lochkarten anpassen können. Es war immer noch eine falsche Designentscheidung.


17
Ich würde nicht so weit gehen zu sagen, dass es ein Designfehler ist . Ich könnte Ihnen zustimmen, dass es eine schlechte Designwahl oder eine Wahl mit unglücklichen Konsequenzen war, aber ich denke immer noch, dass es wie von den Sprachdesignern beabsichtigt funktioniert: Es ermöglicht Ihnen, jedes Unicode-Zeichen an einer beliebigen Stelle in der Datei zu verwenden, während die ASCII-Codierung beibehalten wird der Datei.
Aioobe

12
Trotzdem denke ich, dass die Wahl der Verarbeitungsstufe für \uweniger absurd war als die Entscheidung, Cs Führung bei der Verwendung führender Nullen für die Oktalnotation zu folgen. Obwohl die Oktalschreibweise manchmal nützlich ist, habe ich noch niemanden gehört, der ein Argument formuliert, warum eine führende Null eine gute Möglichkeit ist, dies anzuzeigen.
Supercat

3
@supercat Die Leute, die dieses Feature in C89 geworfen haben, haben das Verhalten des ursprünglichen K & R-Präprozessors verallgemeinert, anstatt ein Feature von Grund auf neu zu entwerfen. Ich bezweifle, dass sie mit den Best Practices für Lochkarten vertraut waren, und ich bezweifle auch, dass die Funktion jemals für den angegebenen Zweck verwendet wurde, außer vielleicht für ein oder zwei Retrocomputing-Übungen.
zwol

8
@supercat Ich hätte kein Problem mit Java \uals Transformation vor der Tokenisierung, wenn es verboten wäre, Zeichen im Bereich U + 0000..U + 007F zu erzeugen. Es ist die Kombination aus "das funktioniert überall" und "dies aliasisiert ASCII-Zeichen mit syntaktischer Bedeutung", die es von umständlich zu völlig falsch herabsetzt.
zwol

4
Zu Ihrem "für Pedanten": Natürlich gab es zu diesem Zeitpunkt den //einzeiligen Kommentar nicht . Und da C eine Erklärung Terminator hat , die nicht eine neue Linie ist, wäre es vor allem für lange Strings verwendet werden, mit der Ausnahme , dass so weit wie ich „Stringliteral Verkettung“ bestimmen kann , war es von K & R.
Mark Hurd

22

Dies war eine absichtliche Designentscheidung, die bis zum ursprünglichen Design von Java zurückreicht.

Für diejenigen, die fragen: "Wer möchte, dass Unicode in Kommentaren entkommt?", Sind sie vermutlich Leute, deren Muttersprache den lateinischen Zeichensatz verwendet. Mit anderen Worten, es ist dem ursprünglichen Design von Java inhärent, dass Leute beliebige Unicode-Zeichen verwenden können, wo immer dies in einem Java-Programm zulässig ist, am typischsten in Kommentaren und Zeichenfolgen.

Es ist wohl ein Mangel in Programmen (wie IDEs), die zum Anzeigen des Quelltextes verwendet werden, dass solche Programme die Unicode-Escapezeichen nicht interpretieren und die entsprechende Glyphe anzeigen können.


8
Heutzutage verwenden wir UTF-8 für unseren Quellcode und können die Unicode-Zeichen direkt verwenden, ohne dass Escapezeichen erforderlich sind.
Paŭlo Ebermann

21

Ich stimme @zwol zu, dass dies ein Designfehler ist. aber ich bin noch kritischer.

\uEscape ist in String- und Char-Literalen nützlich. und das ist der einzige Ort, an dem es existieren sollte. Es sollte genauso gehandhabt werden wie andere Fluchten wie \n; und "\u000A" sollte genau bedeuten "\n".

Es hat absolut keinen Sinn, \uxxxxKommentare zu haben - das kann niemand lesen.

Ebenso macht es keinen Sinn, \uxxxxin einem anderen Teil des Programms zu verwenden. Die einzige Ausnahme besteht wahrscheinlich in öffentlichen APIs, die gezwungen sind, einige Nicht-ASCII-Zeichen zu enthalten - was haben wir das letzte Mal gesehen?

Die Designer hatten ihre Gründe im Jahr 1995, aber 20 Jahre später scheint dies eine falsche Wahl zu sein.

(Frage an die Leser - warum erhält diese Frage immer wieder neue Stimmen? Ist diese Frage von einem beliebten Ort aus verknüpft?)


5
Ich denke, Sie hängen nicht herum, wo Nicht-ASCII-Zeichen in APIs verwendet werden. Es gibt Leute, die es benutzen (nicht ich), zB in asiatischen Ländern. Und wenn Sie Nicht-ASCII-Zeichen in Bezeichnern verwenden, ist es wenig sinnvoll, sie in Dokumentationskommentaren zu verbieten. Dennoch sind es verschiedene Dinge, sie in einem Token zuzulassen und ihnen zu erlauben, die Bedeutung oder Grenze eines Tokens zu ändern.
Holger

15
Sie können die richtige Dateicodierung verwenden. Warum schreiben, int \u5431wenn Sie könnenint 整
ZhongYu

3
Was werden Sie tun, wenn Sie Code anhand ihrer API kompilieren müssen und nicht die richtige Codierung verwenden können (vorausgesetzt, 1995 gab es keine umfassende UTF-8Unterstützung). Sie müssen nur eine Methode aufrufen und möchten nicht das Support Pack für die asiatische Sprache Ihres Betriebssystems (denken Sie daran, die neunziger Jahre) für diese einzelne Methode installieren…
Holger

5
Was jetzt viel klarer als 1995 ist, ist, dass Sie besser Englisch können, wenn Sie programmieren möchten. Das Programmieren ist eine internationale Interaktion, und fast alle Ressourcen sind auf Englisch.
ZhongYu

8
Ich glaube nicht, dass sich das geändert hat. Die Dokumentation von Java war die meiste Zeit auch nur in Englisch. Es gab eine japanische Übersetzung, die für eine Weile beibehalten wurde, aber die Beibehaltung von zwei Sprachen stützt nicht wirklich die Idee, sie für alle Regionen der Welt beizubehalten (sie hat sie eher widerlegt). Zuvor gab es ohnehin keine Mainstream-Sprache mit Unicode-Unterstützung für Bezeichner. Ich würde also vermuten, dass jemand dachte , lokalisierter Quellcode sei das nächste große Ding. Ich würde zum Glück sagen , es ist nicht gestartet.
Holger

11

Die einzigen Personen, die antworten können, warum Unicode-Escapezeichen so implementiert wurden, wie sie waren, sind die Personen, die die Spezifikation geschrieben haben.

Ein plausibler Grund dafür ist, dass der Wunsch bestand, das gesamte BMP als mögliche Zeichen des Java-Quellcodes zuzulassen. Dies stellt jedoch ein Problem dar:

  • Sie möchten ein beliebiges BMP-Zeichen verwenden können.
  • Sie möchten in der Lage sein, jeden BMP-Charater relativ einfach einzugeben. Eine Möglichkeit, dies zu tun, sind Unicode-Escapezeichen.
  • Sie möchten, dass die lexikalische Spezifikation für Menschen leicht zu lesen und zu schreiben und auch relativ einfach zu implementieren ist.

Dies ist unglaublich schwierig, wenn Unicode-Fluchten in den Kampf ziehen: Es werden eine ganze Menge neuer Lexer-Regeln erstellt.

Der einfache Ausweg besteht darin, das Lexen in zwei Schritten durchzuführen: Suchen und ersetzen Sie zuerst alle Unicode-Escapezeichen durch das Zeichen, das sie darstellen, und analysieren Sie dann das resultierende Dokument, als ob Unicode-Escapezeichen nicht vorhanden wären.

Das Beste daran ist, dass es einfach zu spezifizieren ist, wodurch die Spezifikation einfacher und einfacher zu implementieren ist.

Der Nachteil ist Ihr Beispiel.


2
Oder beschränken Sie die Verwendung von \ uxxxx auf Bezeichner, Zeichenfolgenliterale und Zeichenkonstanten. Welches ist, was C11 tut.
Ninjalj

Das kompliziert die Parser-Regeln jedoch wirklich, denn diese definieren diese Dinge. Was ich spekuliere, ist ein Teil des Grundes, warum es so ist, wie es ist.
Martijn
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.