Mengapa mengeksekusi kode Java dalam komentar dengan karakter Unicode tertentu diizinkan?

1356

Kode berikut menghasilkan output "Hello World!" (tidak juga, coba).

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

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

Alasan untuk ini adalah bahwa kompiler Java mem-parsing karakter Unicode \u000dsebagai baris baru dan ditransformasikan menjadi:

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

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

Sehingga menghasilkan komentar yang "dieksekusi".

Karena ini dapat digunakan untuk "menyembunyikan" kode berbahaya atau apa pun yang bisa dipahami oleh seorang programmer jahat, mengapa diizinkan dalam komentar ?

Mengapa ini diizinkan oleh spesifikasi Java?

Reg
sumber
44
"Mengapa ini dibiarkan" tampaknya terlalu berdasarkan pendapat saya. Desainer bahasa membuat keputusan, apa lagi yang perlu diketahui? Kecuali Anda menemukan pernyataan orang yang membuat keputusan itu, kami hanya bisa berspekulasi.
Ingo Bürk
194
Satu hal yang menarik adalah bahwa IDE OP jelas salah dan menampilkan penyorotan yang salah,
dhke
14
Mungkin terkait: stackoverflow.com/questions/4448180/…
dhke
47
@Tobb Tapi desainer Java mengunjungi SO sehingga dimungkinkan untuk mendapatkan jawaban dari salah satu dari mereka. Juga mereka mungkin ada sumber daya yang sudah menjawab pertanyaan ini.
Pshemo
41
Jawaban sederhananya adalah bahwa kode tersebut tidak ada dalam komentar sama sekali, oleh aturan bahasa, jadi pertanyaannya tidak jelas.
Marquis of Lorne

Jawaban:

741

Dekode Unicode dilakukan sebelum terjemahan leksikal lainnya. Manfaat utama dari ini adalah membuatnya sepele untuk bolak-balik antara ASCII dan pengkodean lainnya. Anda bahkan tidak perlu mencari tahu di mana komentar mulai dan berakhir!

Sebagaimana dinyatakan dalam JLS Bagian 3.3 ini memungkinkan alat berbasis ASCII untuk memproses file sumber:

[...] Bahasa pemrograman Java menentukan cara standar untuk mengubah program yang ditulis dalam Unicode menjadi ASCII yang mengubah program menjadi bentuk yang dapat diproses oleh alat berbasis ASCII. [...]

Ini memberikan jaminan mendasar untuk independensi platform (independensi set karakter yang didukung) yang selalu menjadi tujuan utama untuk platform Java.

Mampu menulis karakter Unicode di mana saja dalam file adalah fitur yang rapi, dan terutama penting dalam komentar, ketika mendokumentasikan kode dalam bahasa non-latin. Fakta bahwa ia dapat mengganggu semantik dengan cara-cara halus seperti itu hanyalah efek samping (yang tidak menguntungkan).

Ada banyak gotcha pada tema ini dan Java Puzzlers oleh Joshua Bloch dan Neal Gafter memasukkan varian berikut:

Apakah ini program Java yang legal? Jika demikian, apa yang dicetaknya?

\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

(Program ini ternyata menjadi program "Hello World" biasa-biasa saja.)

Dalam solusi untuk kusut, mereka menunjukkan hal berikut:

Lebih serius, teka-teki ini berfungsi untuk memperkuat pelajaran dari tiga sebelumnya: Pelarian Unicode sangat penting ketika Anda perlu memasukkan karakter yang tidak dapat diwakili dengan cara lain ke dalam program Anda. Hindari mereka dalam semua kasus lainnya.


Sumber: Java: Menjalankan kode dalam komentar ?!

aioobe
sumber
84
Singkatnya, Java sengaja mengizinkannya: "bug" ada di OP's IDE?
Batsyeba
60
@Bathsheba: Ini lebih banyak di kepala orang. Orang tidak mencoba memahami cara kerja parsing Java, sehingga IDE terkadang menampilkan kode dengan cara yang salah. Pada contoh di atas, komentar harus diakhiri dengan \u000ddan bagian setelahnya harus memiliki highlight kode.
Aaron Digulla
62
Kesalahan umum lainnya adalah menyisipkan jalur Windows dalam kode seperti // C:\user\...yang mengarah ke kesalahan kompilasi karena \userbukan urutan keluar Unicode yang valid.
Aaron Digulla
50
Dalam gerhana Kode setelah \u000ddisorot sebagian. Setelah menekan Ctrl + Shift + F karakter diganti dengan baris baru dan sisa baris dibungkus
bluelDe
20
@TheLostMind Jika saya memahami jawaban dengan benar, Anda harus dapat mereproduksi ini dengan komentar blok juga. \u002A/harus mengakhiri komentar.
Taemyr
141

Karena ini belum dibahas, inilah penjelasan, mengapa terjemahan Unicode lolos terjadi sebelum pemrosesan kode sumber lainnya:

Gagasan di baliknya adalah memungkinkan terjemahan kode sumber Java tanpa kehilangan antar berbagai penyandian karakter. Saat ini, ada dukungan Unicode luas, dan ini tidak terlihat seperti masalah, tetapi saat itu tidak mudah bagi pengembang dari negara barat untuk menerima beberapa kode sumber dari rekannya di Asia yang berisi karakter Asia, buat beberapa perubahan ( termasuk mengkompilasi dan mengujinya) dan mengirim hasilnya kembali, semua tanpa merusak sesuatu.

Jadi, kode sumber Java dapat ditulis dalam penyandian apa saja dan memungkinkan berbagai karakter dalam pengidentifikasi, karakter dan Stringliteral serta komentar. Kemudian, untuk mentransfernya tanpa kehilangan, semua karakter yang tidak didukung oleh target encoding digantikan oleh Unicode escapes mereka.

Ini adalah proses yang dapat dibalik dan yang menarik adalah bahwa terjemahan dapat dilakukan oleh alat yang tidak perlu tahu apa-apa tentang sintaks kode sumber Java karena aturan terjemahan tidak bergantung padanya. Ini berfungsi sebagai terjemahan ke karakter Unicode mereka yang sebenarnya di dalam kompiler yang terjadi secara independen ke sintaksis kode sumber Java juga. Ini menyiratkan bahwa Anda dapat melakukan sejumlah langkah penerjemahan secara sewenang-wenang di kedua arah tanpa pernah mengubah arti kode sumber.

Ini adalah alasan untuk fitur aneh lain yang bahkan belum disebutkan: \uuuuuuxxxxsintaks:

Ketika alat terjemahan keluar dari karakter dan menemukan urutan yang sudah keluar urutan, itu harus memasukkan tambahan uke dalam urutan, konversi \ucafeke \uucafe. Makna tidak berubah, tetapi ketika mengkonversi ke arah lain, alat harus menghapus satu udan mengganti hanya urutan yang mengandung satu uoleh karakter Unicode mereka. Dengan begitu, bahkan pelolosan Unicode dipertahankan dalam bentuk aslinya saat mengonversi bolak-balik. Saya kira, tidak ada yang pernah menggunakan fitur itu ...

Holger
sumber
1
Menariknya, native2asciisepertinya tidak menggunakan \uu...xxxxsintaks,
ninjalj
5
Ya, native2asciidimaksudkan untuk membantu menyiapkan bundel sumber daya dengan mengonversinya menjadi iso-latin-1 seperti Properties.loadyang ditetapkan hanya untuk membaca latin-1. Dan di sana, aturannya berbeda, tidak ada \uuu…sintaksis dan tidak ada tahap pemrosesan awal. Dalam file properti, property=multi\u000alinememang sama dengan property=multi\nline. (Bertentangan dengan frasa “menggunakan pelepasan Unicode sebagaimana didefinisikan dalam bagian 3.3 dari Spesifikasi Bahasa Java ™” dari dokumentasi)
Holger
10
Perhatikan bahwa tujuan desain ini dapat dicapai tanpa kutil; cara termudah adalah melarang \upelarian untuk menghasilkan karakter dalam kisaran U + 0000–007F. (Semua karakter seperti itu dapat diwakili secara asli oleh semua penyandian nasional yang relevan pada 1990-an — yah, mungkin kecuali beberapa karakter kontrol, tetapi Anda tetap tidak perlu menulis karakter Java.)
zwol
3
@ zwol: well, jika Anda mengecualikan karakter kontrol yang tidak diizinkan dalam kode sumber Java, Anda benar. Namun demikian, hal itu menyiratkan membuat aturan menjadi lebih rumit. Dan hari ini, sudah terlambat untuk membahas keputusan ...
Holger
ah masalah menyimpan dokumen dalam utf8 dan bukan latin atau sesuatu yang lain. Semua database saya rusak juga karena omong kosong barat ini
David 天宇 Wong
106

Saya akan benar-benar menambahkan poin secara tidak efektif, hanya karena saya tidak dapat menahan diri dan belum melihatnya, bahwa pertanyaannya tidak valid karena mengandung premis tersembunyi yang salah, yaitu bahwa kode berada di komentar!

Dalam kode sumber Java \ u000d setara dengan setiap cara untuk karakter ASCII CR. Ini adalah akhiran garis, jelas dan sederhana, di mana pun itu terjadi. Pemformatan dalam pertanyaan ini menyesatkan, urutan karakter apa yang secara sintaksis bersesuaian adalah:

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

Oleh karena itu, jawaban yang paling benar adalah: kode dijalankan karena tidak ada dalam komentar; itu di baris berikutnya. "Kode pelaksana dalam komentar" tidak diizinkan di Jawa, seperti yang Anda harapkan.

Sebagian besar kebingungan berasal dari fakta bahwa highlighter sintaks dan IDE tidak cukup canggih untuk mempertimbangkan situasi ini. Mereka juga tidak memproses unicode lolos sama sekali, atau mereka melakukannya setelah parsing kode, bukan sebelumnya, seperti javachalnya.

Pepijn Schmitz
sumber
6
Saya setuju, ini bukan "kesalahan desain" java, tapi ini bug IDE.
bvdb
3
Pertanyaannya agak tentang mengapa kode yang terlihat seperti komentar untuk seseorang yang tidak terbiasa dengan aspek khusus bahasa ini dan mungkin tanpa referensi untuk penyorotan sintaksis, sebenarnya bukan komentar. Keberatan atas dasar premis dari pertanyaan yang tidak sah itu tidak jujur.
Phil
@ Phil: ini hanya tampak seperti komentar ketika dilihat dengan alat tertentu, orang lain menunjukkan sebaliknya.
jmoreno
1
@ jmoreno kita tidak harus memiliki apa pun selain editor teks untuk membaca kode. Paling tidak, itu melanggar prinsip paling tidak mengejutkan, yaitu bahwa // style comment berlanjut hingga karakter berikutnya - tidak ke urutan lain yang akhirnya diganti oleh pada akhirnya. Komentar tidak pernah diharapkan menjadi apa pun selain ditelanjangi. Preprosesor yang salah.
Phil
69

The \u000dmelarikan diri berakhir komentar karena \ulolos secara seragam diubah ke karakter Unicode yang sesuai sebelum program ini tokenized. Anda bisa menggunakan keduanya \u0057\u0057sebagai ganti //untuk memulai komentar.

Ini adalah bug di IDE Anda, yang seharusnya menyoroti garis untuk membuat jelas bahwa\u000d mengakhiri komentar.

Ini juga merupakan kesalahan desain dalam bahasa. Itu tidak dapat diperbaiki sekarang, karena itu akan merusak program yang bergantung padanya. \uescapes harus dikonversi ke karakter Unicode yang sesuai dengan kompilator hanya dalam konteks di mana "masuk akal" (string literal dan pengidentifikasi, dan mungkin tidak ada di tempat lain) atau mereka seharusnya dilarang untuk menghasilkan karakter dalam kisaran U + 0000–007F , atau keduanya. Salah satu dari semantik itu akan mencegah komentar dihentikan oleh \u000dpelarian, tanpa mengganggu kasus-kasus di mana \upelarian berguna - perhatikan bahwa itu termasuk penggunaan \upelarian di dalam komentar sebagai cara untuk menyandikan komentar dalam skrip non-Latin, karena editor teks bisa mengambil pandangan yang lebih luas dari mana\uescapes lebih penting daripada kompiler. (Saya tidak mengetahui ada editor atau IDE yang akan menampilkan \ulolos sebagai karakter yang sesuai dalam apa pun konteksnya.)

Ada kesalahan desain yang serupa dalam keluarga C, 1 di mana backslash-newline diproses sebelum batas komentar ditentukan, jadi misalnya

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

Saya membawa ini untuk menggambarkan bahwa itu mudah untuk membuat kesalahan desain khusus ini, dan tidak menyadari bahwa itu adalah kesalahan sampai sudah terlambat untuk memperbaikinya, jika Anda terbiasa berpikir tentang tokenization dan menguraikan cara programmer compiler berpikir tentang tokenization dan parsing. Pada dasarnya, jika Anda telah mendefinisikan tata bahasa formal Anda dan kemudian seseorang membuat kasus khusus sintaksis - trigraph, backslash-newline, pengkodean karakter Unicode yang sewenang-wenang dalam file sumber terbatas pada ASCII, apa pun - yang perlu dijepit, lebih mudah untuk tambahkan pass transformasi sebelum tokenizer daripada mendefinisikan ulang tokenizer untuk memperhatikan di mana masuk akal untuk menggunakan case khusus itu.

1 Untuk para pengendara: Saya tahu bahwa aspek C ini 100% disengaja, dengan alasan - Saya tidak mengada-ada - bahwa itu akan memungkinkan Anda untuk secara mekanis mencocokkan kode dengan garis panjang sewenang-wenang ke kartu berlubang. Itu masih keputusan desain yang salah.

zwol
sumber
17
Saya tidak akan mengatakan bahwa ini adalah kesalahan desain . Saya dapat setuju dengan Anda bahwa itu adalah pilihan desain yang buruk, atau pilihan dengan konsekuensi yang tidak menguntungkan, tetapi saya masih berpikir bahwa itu berfungsi sesuai dengan perancang bahasa yang dimaksudkan: Ini memungkinkan Anda untuk menggunakan karakter unicode di mana saja dalam file, sambil tetap mempertahankan pengkodean ASCII dari file.
aioobe
12
Yang telah dikatakan, saya pikir pilihan tahap pemrosesan \ukurang absurd daripada keputusan untuk mengikuti jejak C dalam menggunakan nol terkemuka untuk notasi oktal. Walaupun notasi oktal kadang berguna, saya belum pernah mendengar ada yang mengartikulasikan argumen mengapa angka nol di depan adalah cara yang baik untuk menunjukkannya.
supercat
3
@supercat Orang-orang yang melempar fitur itu ke C89 sedang menggeneralisasi perilaku preprocessor K&R asli daripada merancang fitur dari awal. Saya ragu mereka terbiasa dengan praktik terbaik kartu punch, dan saya juga meragukan bahwa fitur tersebut pernah digunakan untuk tujuan yang disebutkan, kecuali mungkin untuk satu atau dua latihan retrocomputing.
zwol
8
@supercat Saya tidak akan memiliki masalah dengan Java \usebagai transformasi pra-tokenisasi jika dilarang untuk menghasilkan karakter dalam kisaran U + 0000..U + 007F. Ini kombinasi dari "ini bekerja di mana-mana" dan "ini alias karakter ASCII dengan signifikansi sintaksis" yang menurunkannya dari salah canggung menjadi salah.
zwol
4
Pada "untuk pedant" Anda: Tentu saja pada saat itu //komentar single-line tidak ada . Dan karena C memiliki terminator pernyataan yang bukan baris baru, sebagian besar akan digunakan untuk string panjang, kecuali bahwa sejauh yang saya bisa menentukan "string literal concatenation" ada di sana dari K&R.
Mark Hurd
22

Ini adalah pilihan desain yang disengaja yang akan kembali ke desain asli Jawa.

Untuk orang-orang yang bertanya "siapa yang ingin Unicode lolos dalam komentar?", Saya kira mereka adalah orang-orang yang bahasa ibunya menggunakan set karakter Latin. Dengan kata lain, itu melekat dalam desain asli Jawa bahwa orang dapat menggunakan karakter Unicode sewenang-wenang di mana pun legal dalam program Java, biasanya dalam komentar dan string.

Ini bisa dibilang kekurangan dalam program (seperti IDE) yang digunakan untuk melihat teks sumber bahwa program tersebut tidak dapat menafsirkan Unicode lolos dan menampilkan mesin terbang yang sesuai.

Jonathan Gibbons
sumber
8
Saat ini kami menggunakan UTF-8 untuk kode sumber kami, dan dapat menggunakan karakter Unicode secara langsung, tidak perlu untuk melarikan diri.
Paŭlo Ebermann
21

Saya setuju dengan @zwol bahwa ini adalah kesalahan desain; tapi saya bahkan lebih kritis terhadapnya.

\umelarikan diri berguna dalam string dan char literal; dan itulah satu-satunya tempat yang seharusnya ada. Itu harus ditangani dengan cara yang sama seperti pelarian lainnya seperti \n; dan "\u000A" harus berarti persis "\n".

Sama sekali tidak ada gunanya \uxxxxberkomentar - tidak ada yang bisa membacanya.

Demikian pula, tidak ada gunanya menggunakan \uxxxx di bagian lain dari program ini. Satu-satunya pengecualian mungkin di API publik yang dipaksa mengandung beberapa karakter non-ascii - apa yang terakhir kali kita lihat itu?

Para desainer memiliki alasan mereka pada tahun 1995, tetapi 20 tahun kemudian, ini tampaknya menjadi pilihan yang salah.

(pertanyaan kepada pembaca - mengapa pertanyaan ini terus mendapatkan suara baru? Apakah pertanyaan ini ditautkan dari tempat yang populer?)

ZhongYu
sumber
5
Saya kira, Anda tidak berkeliaran, di mana karakter non-ASCII digunakan dalam API. Ada orang yang menggunakannya (bukan saya), misalnya di negara-negara Asia. Dan ketika Anda menggunakan karakter non-ASCII dalam pengidentifikasi, melarang mereka dalam komentar dokumentasi tidak masuk akal. Namun demikian, membiarkan mereka di dalam token dan memungkinkan mereka untuk mengubah makna atau batas token adalah hal yang berbeda.
Holger
15
mereka dapat menggunakan penyandian file yang tepat. mengapa menulis int \u5431ketika Anda bisa melakukannyaint 整
ZhongYu
3
Apa yang akan Anda lakukan ketika Anda harus mengkompilasi kode terhadap API mereka dan tidak dapat menggunakan pengkodean yang tepat (menganggap bahwa tidak ada UTF-8dukungan luas pada tahun 1995). Anda hanya perlu memanggil satu metode dan tidak ingin menginstal paket dukungan bahasa Asia dari sistem operasi Anda (ingat, tahun sembilan puluhan) untuk metode tunggal itu ...
Holger
5
Yang jauh lebih jelas sekarang daripada 1995 adalah Anda lebih tahu bahasa Inggris jika Anda ingin memprogram. Pemrograman adalah interaksi internasional, dan hampir semua sumber daya dalam bahasa Inggris.
ZhongYu
8
Saya tidak berpikir ini telah berubah. Dokumentasi Java sebagian besar bahasa Inggris juga. Ada terjemahan bahasa Jepang yang dipertahankan untuk sementara waktu tetapi mempertahankan dua bahasa tidak benar-benar mendukung gagasan mempertahankannya untuk semua lokal di dunia (itu agak membantahnya). Dan sebelum itu, tidak ada bahasa utama dengan dukungan Unicode di pengidentifikasi pula. Jadi saya kira, seseorang berpikir bahwa kode sumber lokal adalah hal besar berikutnya. Saya akan mengatakan untungnya , itu tidak lepas landas.
Holger
11

Satu-satunya orang yang dapat menjawab mengapa Unicode lolos adalah mereka adalah orang-orang yang menulis spesifikasi.

Alasan yang masuk akal untuk ini adalah bahwa ada keinginan untuk mengizinkan seluruh BMP sebagai karakter yang mungkin dari kode sumber Java. Ini menghadirkan masalah:

  • Anda ingin dapat menggunakan karakter BMP apa pun.
  • Anda ingin dapat memasukkan charater BMP apa saja dengan cukup mudah. Cara untuk melakukan ini adalah dengan lolos Unicode.
  • Anda ingin menjaga spesifikasi leksikal mudah bagi manusia untuk membaca dan menulis, dan juga cukup mudah untuk diterapkan.

Ini sangat sulit ketika Unicode lolos memasuki keributan: itu menciptakan seluruh beban aturan lexer baru.

Jalan keluar yang mudah adalah dengan melakukan lexing dalam dua langkah: pertama mencari dan mengganti semua Unicode lolos dengan karakter yang diwakilinya, dan kemudian mengurai dokumen yang dihasilkan seolah-olah Unicode lolos tidak ada.

Sisi positifnya adalah mudah ditentukan, sehingga membuat spesifikasi lebih sederhana, dan mudah diterapkan.

Kelemahannya adalah, contoh Anda.

Martijn
sumber
2
Atau, batasi penggunaan \ uxxxx untuk pengidentifikasi, string literal, dan konstanta karakter. Itulah yang dilakukan C11.
ninjalj
yang benar-benar menyulitkan aturan parser, karena itulah yang mendefinisikan hal-hal itu, yang saya berspekulasi adalah bagian dari alasannya.
Martijn