為什麼在允許某些Unicode字符的註釋中執行Java代碼?




comments (6)

以下代碼生成輸出“Hello World!” (不,真的,試試吧)。

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

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

原因是Java編譯器將Unicode字符 \u000d 解析為新行並轉換為:

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

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

從而導致評論被“執行”。

由於這可以用來“隱藏”惡意代碼或惡意程序員可以設想的任何東西, 為什麼在評論中允許它

為什麼Java規範允許這樣做?


Unicode解碼在任何其他詞彙翻譯之前進行。 這樣做的主要好處是可以在ASCII和任何其他編碼之間來回切換。 你甚至不需要弄清楚評論的開始和結束位置!

JLS第3.3節 所述,這允許任何基於ASCII的工具處理源文件:

[...] Java編程語言指定了一種將用Unicode編寫的程序轉換為ASCII的標準方法,該程序將程序更改為可由基於ASCII的工具處理的形式。 [...]

這為平台獨立性(支持的字符集的獨立性)提供了基本保證,這一直是Java平台的關鍵目標。

能夠在文件中的任何位置編寫任何Unicode字符是一個很好的功能,在使用非拉丁語言編寫代碼時,在評論中尤其重要。 它以這種微妙的方式乾擾語義的事實只是(不幸的)副作用。

關於這個主題有許多問題,Joshua Bloch和Neal Gafter的 Java Puzzlers 包括以下變體:

這是一個合法的Java程序嗎? 如果是這樣,它會打印什麼?

\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

(這個程序原來是一個簡單的“Hello World”程序。)

在解決益智遊戲的過程中,他們指出了以下內容:

更嚴重的是,這個謎題有助於強化前三個課程的教訓: 當您需要插入無法以任何其他方式表示的字符時,Unicode轉義是必不可少的。 在所有其他情況下避免它們。

來源: Java:在評論中執行代碼?!


我同意@zwol這是一個設計錯誤; 但我更加批評它。

\u 轉義在字符串和字符文字中很有用; 這是唯一應該存在的地方。 它應該像其他轉義一樣處理,如 \n ; 而 "\u000A" 應該 恰好代表 "\n"

絕對沒有 \uxxxx 在評論中使用 \uxxxx - 沒有人可以閱讀。

同樣,在程序的其他部分使用 \uxxxx 也沒有意義。 唯一的例外可能是在強制包含一些非ascii字符的公共API中 - 我們最後一次看到它是什麼?

設計師在1995年有他們的理由,但20年後,這似乎是一個錯誤的選擇。

(向讀者提問 - 為什麼這個問題不斷獲得新的選票?這個問題是否從流行的地方聯繫起來?)


由於尚未解決,這裡有一個解釋,為什麼Unicode轉義的轉換發生在任何其他源代碼處理之前:

它背後的想法是它允許在不同的字符編碼之間無損地翻譯Java源代碼。 今天,有廣泛的Unicode支持,這看起來不是一個問題,但是當時西方國家的開發人員從他的亞洲同事那裡收到一些包含亞洲字符的源代碼並不容易做出一些改變(包括編譯和測試它並將結果發回,所有這些都不會損壞。

因此,Java源代碼可以用任何編碼編寫,並允許標識符,字符和 String 文字和註釋中的各種字符。 然後,為了無損地傳輸它,目標編碼不支持的所有字符都被它們的Unicode轉義替換。

這是一個可逆的過程,有趣的是,轉換可以通過一個工具來完成,該工具不需要知道任何關於Java源代碼語法的知識,因為轉換規則不依賴於它。 這適用於編譯器內部實際Unicode字符的轉換也獨立於Java源代碼語法。 這意味著您可以在兩個方向上執行任意數量的轉換步驟,而無需更改源代碼的含義。

這就是另一個奇怪的功能,甚至沒有提到的原因: \uuuuuuxxxx 語法:

當翻譯工具轉義字符並遇到已經是轉義序列的序列時,它應該在序列中插入一個額外的 u ,將 \ucafe 轉換為 \uucafe 。 意思不會改變,但是當轉換到另一個方向時,工具應該只刪除一個 u 並僅用Unicode字符替換包含單個 u 的序列。 這樣,即使Unicode轉義在來迴轉換時也會以原始形式保留。 我想,沒有人曾經使用過這個功能......


編譯器不僅會在將程序解析為標記之前將Unicode轉義轉換為它們所代表的字符,而是在丟棄註釋和空格之前進行轉換。

該程序包含一個Unicode轉義符(\ u000d),位於其唯一註釋中。 正如評論告訴您的那樣,此轉義表示換行符,並且編譯器 在放棄註釋之前 正確轉換它。

這與平台有關。 在某些平台上,例如UNIX,它可以工作; 在其他方面,例如Windows,它不會。 雖然肉眼可能看起來相同,但如果將其保存在文件中或通過管道傳輸到另一個程序進行後續處理,則很容易引起問題。


這是一個有意的設計選擇,一直回到Java的原始設計。

對於那些問“誰想要在評論中逃脫Unicode?”的人,我認為他們是那些母語使用拉丁字符集的人。 換句話說,Java的原始設計中固有的,人們可以在Java程序中的任何合法地方使用任意Unicode字符,最常見的是在註釋和字符串中。

可以說,用於查看源文本的程序(如IDE)的缺點是這些程序無法解釋Unicode轉義並顯示相應的字形。


\u000d 轉義終止註釋,因為 \u 轉義在程序被標記化 之前 被統一轉換為相應的Unicode字符。 您也可以使用 \u0057\u0057 而不是 // 開始 評論。

這是IDE中的一個錯誤,它應該語法突出顯示該行,以明確 \u000d 結束註釋。

這也是語言中的設計錯誤。 它現在無法糾正,因為這會破壞依賴它的程序。 \u 轉義應該由編譯器僅在“有意義”的字符串中轉換為相應的Unicode字符(字符串文字和標識符,可能不在其他地方),或者它們應該被禁止在U + 0000-中生成字符007F範圍,或兩者兼而有之。 這些語義中的任何一個都會阻止註釋被 \u000d 轉義終止,而不會干擾 \u 轉義符有用的情況 - 請注意,這 包括 在註釋中使用 \u 轉義作為在非轉義中編碼註釋的方法-Latin腳本,因為文本編輯器可以更廣泛地了解 \u 轉義比編譯器更重要的地方。 (我不知道任何編輯器或IDE會在 任何 上下文中顯示 \u 轉義為相應的字符。)

在C系列中存在類似的設計錯誤, 1 其中在確定註釋邊界之前處理反斜杠換行符,例如

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

我提出這個問題來說明這個特定的設計錯誤很容易發生,並且如果你習慣於考慮標記化和解析編譯程序員的思維方式,那麼直到修正它為時已經太晚才會發現它是錯誤的。關於標記化和解析。 基本上,如果你已經定義了你的形式語法,然後有人想出一個語法特殊情況 - trigraphs,反斜杠換行,在源文件中編碼任意Unicode字符,限制為ASCII,無論什麼 - 需要楔入,它更容易在令牌化器 之前 添加轉​​換傳遞 而不是重新定義令牌化器以注意使用該特殊情況的合理位置。

1 對於學齡兒童:我知道C的這個方面是100%有意識的,理由是 - 我不是這樣做的 - 它可以讓你用任意長線機械強制編碼代碼到打孔卡上。 這仍然是一個不正確的設計決定。







comments