Tulis kode java untuk mendeteksi versi JVM

17

Tujuannya adalah untuk menulis kode java yang mendeteksi versi JVM dengan mengandalkan perubahan kompatibilitas, efek samping, bug dan / atau perilaku tidak terdefinisi yang bekerja dengan cara dalam satu versi dan cara lain di versi lain. Selanjutnya, kode harus setidaknya sedikit terbaca, tanpa mengorbankan spasi putih dan nama variabel yang dapat dibaca.

Untuk memastikan tujuan itu, aturan formal yang tepat adalah:

  1. Kode harus ditulis dalam java dan harus menampilkan versi JRE di mana ia berjalan.

  2. Kode tidak boleh menggunakan JDK atau JRE API yang disediakan khusus untuk mendeteksi versi java atau yang memberikan versi JDK atau JRE secara gratis.

  3. Kode tidak boleh menggunakan refleksi.

  4. Kode ini hanya diperlukan untuk bekerja di Hotspot Java SE 5, 6 dan 7, tetapi dapat bekerja di JVM lainnya.

  5. Kode tidak boleh menggunakan perpustakaan pihak ketiga apa pun di classpath.

  6. Kode tidak boleh memulai proses lain, java atau tidak.

  7. Kode tidak boleh menggunakan variabel lingkungan.

  8. Kode tidak boleh mencari sistem file untuk mencari file atau folder yang sudah ada.

  9. Kode harus dimuat dalam satu file dan dipanggil melalui public static void main(String[] args)atau public static void main(String... args).

  10. Kode tidak boleh menggunakan API non-publik apa pun yang ada di JRE.

  11. Kode tidak boleh menghasilkan NoClassDefFoundError, NoSuchMethodError, ClassNotFoundException atau NoSuchMethodException selama eksekusi.

  12. Kode harus dijalankan dalam sistem yang terputus dari internet atau dari jaringan lokal apa pun.

  13. Anda harus memberikan penjelasan mengapa berperilaku dalam satu cara dalam versi dan cara lain di versi lain.

Mencetak gol

Metode yang digunakan untuk mengukur solusi terbaik adalah maks (n / s), di mana n adalah jumlah versi java yang berbeda yang terdeteksi tanpa melanggar salah satu aturan ini (setidaknya versi 5, 6 dan 7) dan s adalah jumlah token leksikal dalam solusinya.

Victor Stafusa
sumber
Tidak dapat menemukan tag yang lebih baik, dan saya harus memberikan paling tidak dua. Lebih lanjut, saya tidak punya cukup perwakilan untuk membuat tag baru. Alasan untuk java adalah karena itu adalah bahasa yang sangat portabel, jadi menulis itu akan sangat menarik. Lebih jauh lagi, versi java didefinisikan dengan cara yang bisa kita bandingkan entri mendeteksi lingkungan dengan keseragaman, tanpa harus membandingkan jeruk dengan apel.
Victor Stafusa
Anda dapat mempertimbangkan [secara curang] dengan alasan bahwa deteksi versi VM adalah langkah dalam menyerang sistem. Saya tidak bisa mengatakan bahwa saya punya saran lain.
dmckee
@dmckee Menjatuhkan tag [code-golf]. Tambahkan tag [curang]. Bisakah Anda membuat tag [java]?
Victor Stafusa
4
Saya memberikan suara untuk menutup pertanyaan ini sebagai di luar topik karena tantangan curang tidak lagi pada topik di situs ini. meta.codegolf.stackexchange.com/a/8326/20469
cat
@ kucing, Anda seharusnya menghapus tag, karena tidak sesuai dengan pertanyaan.
Peter Taylor

Jawaban:

9

6/102 = 0,0588

Mendeteksi 6 versi. Memiliki 102 token leksikal (turun dari 103, setelah saya dihapus publicdi public class).

import java.security.Signature;

class GuessVersion {
        public static void main(String[] args) {
                String version = "Java 1.1";
                try {
                        "".getBytes("ISO8859_13");
                        version = "Java 1.3";

                        "".getBytes("ISO8859_15");
                        version = "Java 1.4";

                        Signature.getInstance("SHA256withRSA");
                        version = "Java 5";

                        "".getBytes("KOI8_U");
                        version = "Java 6";

                        Signature.getInstance("SHA256withECDSA");
                        version = "Java 7";
                } catch(Exception e) {}
                System.out.println(version);
        }
}

Java 1.1 memperkenalkan pengkodean karakter dan algoritma kriptografi ke Java. Versi selanjutnya menambahkan lebih banyak penyandian dan algoritma. Program ini mencoba menggunakan penyandian dan algoritme hingga ada pengecualian. Saya mengharapkan penyandian yang hilang untuk membuang java.io.UnsupportedEncodingExceptiondan algoritma yang hilang untuk melempar java.security.NoSuchAlgorithmException.

Saya memiliki PowerPC Macintosh lama dengan empat versi lama Java. Mesin OpenBSD saya memiliki dua versi lagi, jadi saya menguji enam versi ini:

  • Java 1.1.8 di MRJ 2.2.6 untuk Mac OS 9.2.2
  • Java 1.3.1_16 untuk Mac OS X Panther
  • Java 1.4.2_21 untuk Mac OS X Tiger
  • Java 1.5.0_19 untuk Mac OS X Tiger
  • OpenJDK 1.6.0_32 untuk OpenBSD 5.5
  • OpenJDK 1.7.0_21 untuk OpenBSD 5.5

Program ini juga dapat berjalan di JamVM 1.5.4 dan gcj 4.8.2 untuk OpenBSD, tetapi tidak mengidentifikasi mereka sebagai implementasi yang berbeda. Ini hanya mencetak "Java 5".

Runtime Mac OS untuk Java

Berkat "Tulis sekali, jalankan di mana-mana!", Saya dapat menulis program ini sekali, kompilasi sekali, dan jalankan satu GuessVersion.class di semua delapan mesin virtual. Saya memerlukan kompiler untuk Java 1.1, versi tertua dalam koleksi saya.

Kompiler saya adalah javacalat dari MRJ SDK 2.2. Karena Classic Mac OS tidak memiliki baris perintah, javacadalah alat yang cukup grafis di mana saya memilih file dan opsi dan klik "Do Javac". Setelah saya mengedit kode saya, saya cukup klik "Do Javac" lagi.

javac dari MRJ SDK 2.2 untuk Classic Mac OS

Cara termudah untuk menjalankan GuessVersion.class adalah membukanya di JBindery, alat lain dari MRJ SDK 2.2. Runtime adalah MRJ 2.2.6, sebuah implementasi dari Java 1.1.8.

kernigh
sumber
22

Saya tidak yakin berapa skor saya, karena itu tergantung pada apa yang Anda anggap sebagai token leksikal, tapi saya mencoba menyalahgunakan sistem penghitungan sebanyak mungkin dengan string panjang ...

Ini juga tergantung pada apakah Anda menghitung ini sebagai mengidentifikasi 7 versi yang berbeda atau 16 ... (Ini dapat dengan mudah diperpanjang hingga 190).

class V extends ClassLoader
{
    public static void main(String[]args)
    {
        for(byte b=60;;)
            try {
                byte[]buf="\u00ca\u00fe\u00ba\u00be\u0000\u0000\u00002\u0000\u0005\u0007\u0000\u0003\u0007\u0000\u0004\u0001\u0000\u0001A\u0001\u0000\u0010java/lang/Object\u0006\u0000\u0000\u0001\u0000\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000".getBytes("ISO-8859-1");
                buf[7]=b--;
                new V().defineClass(buf,0,53);
                System.out.println(b-43);
                break;
            }
            catch(Throwable t){}
    }
}

Ini bekerja dengan mencoba untuk mendefinisikan antarmuka dalam classloader kustom dengan turun nomor versi utama dari format kelas. Yang pertama yang tidak melempar java.lang.UnsupportedClassVersionErrorkorespondensi dengan versi VM.

Peter Taylor
sumber
Terhitung 84 token. Namun masih belum mengujinya.
Victor Stafusa
Jawaban Anda ramah. Dapat mengurangi menjadi 83 token secara sepele String... args.
Victor Stafusa
@ Viktor, itu akan mempersulit pertanyaan apakah ia mendukung 7 versi yang berbeda bahkan lebih. Saya tidak mengetahui adanya kompiler yang mendukung sintaksis Java 5 dan mengkompilasi ke file kelas yang kompatibel dengan Java 1.
Peter Taylor
Poin bagus. Saya lupa tentang itu.
Victor Stafusa
1
Java 1.1.8 (di MRJ 2.2.6) gagal untuk mengkompilasi ini, sampai saya menambahkan 17 token lebih: protected Class loadClass(String name, boolean resolve) { return Object.class; }. Dokumen API saat ini lalai menyebutkan bagaimana ini adalah metode abstrak sebelum Java 1.2. Saya mengembalikan Object.class karena metode ini mendapat satu panggilan untuk "java.lang.Object".
kernigh
8
class Evil {
    public static void main(String... args) {
        String s1 = "Good";
        s1 += "morning";
        int a = 7;
        if (s1 != s1.intern())
            try {
                a--;
                javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar().equals(null);
            } catch (Throwable e) {
                a--;
            }
        System.out.println(a);
    }
}

Algoritma interning berubah antara Java 6 dan 7. Lihat /programming//a/7224864/540552

XMLGregorianCalendar.equals (null) digunakan untuk melempar NullPointerException di java 5, tapi ini diperbaiki di java 6. Lihat http://bugs.sun.com/view_bug.do?bug_id=6285370

100 96 92 87 85 token di sini. Terima kasih kepada Peter Taylor karena mengurangi 7 token.

Victor Stafusa
sumber
1
Anda dapat menyimpan 3 token dengan menyimpan nomor versi di s1. Anda mungkin dapat menyimpan 2 lebih lanjut dengan menangkap Throwable secara langsung, dengan asumsi bahwa DatatypeConfigurationExceptiontidak akan dibuang.
Peter Taylor
1
Atau lebih baik, simpan int atetapi segera inisialisasi, sehingga ifblok menjadi kosong. Negasikan kondisi tersebut, hapus yang lain, dan gunakan --alih-alih penetapan langsung ke a.
Peter Taylor