Napisz kod Java, aby wykryć wersję JVM

17

Celem jest napisanie kodu Java, który wykrywa wersję JVM polegającą na zmianach kompatybilności, skutkach ubocznych, błędach i / lub niezdefiniowanym zachowaniu, które działa w pewien sposób w jednej wersji i w inny sposób w innej wersji. Ponadto kod powinien być co najmniej nieco czytelny, bez poświęcania białych znaków i czytelnych nazw zmiennych.

Aby zapewnić ten cel, dokładne zasady formalne są następujące:

  1. Kod musi być napisany w Javie i powinien wypisywać wersję JRE, w której jest uruchomiony.

  2. Kod nie może używać żadnego interfejsu JDK lub JRE API specjalnie do wykrywania wersji Java lub który udostępnia wersję JDK lub JRE za darmo.

  3. Kod nie może używać refleksji.

  4. Kod jest wymagany tylko do pracy w Hotspot Java SE 5, 6 i 7, ale może działać w innych maszynach JVM.

  5. Kod nie może używać bibliotek stron trzecich w ścieżce klasy.

  6. Kod nie może uruchamiać żadnego innego procesu, Java lub nie.

  7. Kod nie może używać zmiennych środowiskowych.

  8. Kod nie może przeszukiwać systemu plików w poszukiwaniu wcześniej istniejących plików lub folderów.

  9. Kod musi być zawarty w jednym pliku i wywoływany przez public static void main(String[] args)lub public static void main(String... args).

  10. Kod nie może wykorzystywać żadnych niepublicznych interfejsów API obecnych w środowisku JRE.

  11. Kod nie może generować żadnego NoClassDefFoundError, NoSuchMethodError, ClassNotFoundException lub NoSuchMethodException podczas jego wykonywania.

  12. Kod powinien działać w systemie odłączonym od Internetu lub od dowolnej sieci lokalnej.

  13. Powinieneś wyjaśnić, dlaczego zachowuje się w jeden sposób w jednej wersji, a inny w innej wersji.

Punktacja

Metodą stosowaną do pomiaru najlepszego rozwiązania jest max (n / s), gdzie n oznacza liczbę wykrytych różnych wersji Java bez naruszenia którejkolwiek z tych zasad (co najmniej wersje 5, 6 i 7), a s jest liczbą tokenów leksykalnych w roztworze.

Victor Stafusa
źródło
Nie mogłem znaleźć lepszego tagu i musiałem podać co najmniej dwa. Ponadto nie mam wystarczającej liczby przedstawicieli, aby utworzyć nowe tagi. Powodem jest to, że Java jest prawdopodobnie bardzo przenośnym językiem, więc pisanie byłoby bardzo interesujące. Ponadto wersje java są zdefiniowane w taki sposób, że możemy porównywać wpisy wykrywające środowisko z jednolitością, bez konieczności porównywania pomarańczy z jabłkami.
Victor Stafusa,
Można rozważyć [niedoceniany] argument, że wykrywanie wersji maszyny wirtualnej jest krokiem w atakowaniu systemu. Nie mogę powiedzieć, że mam inną sugestię.
dmckee,
@dmckee Upuścił tag [code-golf]. Dodaj tag [underhanded]. Czy możesz utworzyć tag [java]?
Victor Stafusa,
4
Głosuję za zamknięciem tego pytania jako nie na temat, ponieważ słabe wyzwania nie są już na ten temat na tej stronie. meta.codegolf.stackexchange.com/a/8326/20469
kot
@cat, zamiast tego powinieneś usunąć tag, ponieważ nie pasował do pytania.
Peter Taylor

Odpowiedzi:

9

6/102 = 0,0588

Wykrywa 6 wersji. Posiada 102 tokeny leksykalne (spadek z 103, po Usunąłem publicw 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 wprowadziła kodowanie znaków i algorytmy kryptograficzne w Javie. Późniejsze wersje dodały więcej kodowań i algorytmów. Ten program próbuje używać kodowań i algorytmów, dopóki nie wyłapie wyjątku. Oczekuję, że zostanie wygenerowane brakujące kodowanie java.io.UnsupportedEncodingExceptioni brakujący algorytm java.security.NoSuchAlgorithmException.

Miałem stary Macintosh PowerPC z czterema starymi wersjami Javy. Moja maszyna OpenBSD ma jeszcze dwie wersje, więc przetestowałem te sześć wersji:

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

Ten program może również działać w JamVM 1.5.4 i gcj 4.8.2 dla OpenBSD, ale nie identyfikuje ich jako różnych implementacji. Drukuje tylko „Java 5”.

Środowisko wykonawcze Mac OS dla Java

Dzięki „Napisz raz, uruchom wszędzie!”, Mogę napisać ten program raz, skompilować go raz i uruchomić jedną GuessVersion.class na wszystkich ośmiu maszynach wirtualnych. Potrzebuję kompilatora dla Java 1.1, najstarszej wersji w mojej kolekcji.

Mój kompilator jest javacnarzędziem z MRJ SDK 2.2. Ponieważ Classic Mac OS nie miał wiersza poleceń, javacjest dość graficznym narzędziem, w którym wybieram pliki i opcje i klikam „Do Javac”. Po edycji kodu po prostu ponownie klikam „Do Javac”.

javac z MRJ SDK 2.2 dla Classic Mac OS

Najprostszym sposobem na uruchomienie GuessVersion.class jest otwarcie go w JBindery, innym narzędziu z MRJ SDK 2.2. Środowisko wykonawcze to MRJ 2.2.6, implementacja Java 1.1.8.

kernigh
źródło
22

Nie jestem pewien, jaki jest mój wynik, ponieważ zależy to od tego, co dokładnie uważasz za leksykalny, ale staram się nadużywać tego systemu liczenia tak długo, jak to możliwe, długim ciągiem ...

Zależy to również od tego, czy liczysz to jako identyfikację 7 różnych wersji, czy 16 ... (Można to trywialnie rozszerzyć do 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){}
    }
}

Działa, próbując zdefiniować interfejs w niestandardowym module ładującym klasy z malejącymi głównymi numerami wersji formatu klasy. Pierwszy, który nie rzuca, java.lang.UnsupportedClassVersionErrorodpowiada wersji maszyny wirtualnej.

Peter Taylor
źródło
Policzono 84 tokeny. Nadal tego nie testowałem.
Victor Stafusa,
Twoja odpowiedź jest genialna. Można trywialnie zmniejszyć do 83 tokenów za pomocą String... args.
Victor Stafusa,
@Victor, skomplikowałoby to pytanie, czy obsługuje jeszcze 7 różnych wersji. Nie znam żadnego kompilatora, który obsługuje składnię Java 5 i kompiluje do plików klas zgodnych z Java 1.
Peter Taylor,
Słuszna uwaga. Zapomniałem o tym.
Victor Stafusa
1
Java 1.1.8 (w MRJ 2.2.6) nie gromadzi te, aż dodano dodatkowe 17 symboli: protected Class loadClass(String name, boolean resolve) { return Object.class; }. Obecne dokumenty API nie wspominają o tym, jak była to abstrakcyjna metoda przed Javą 1.2. Zwracam Object.class, ponieważ metoda otrzymuje jedno wywołanie „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);
    }
}

Algorytm internowania zmienił się między Java 6 a 7. Zobacz /programming//a/7224864/540552

XMLGregorianCalendar.equals (null) używany do zgłaszania wyjątku NullPointerException w java 5, ale zostało to naprawione w java 6. Zobacz http://bugs.sun.com/view_bug.do?bug_id=6285370

100 96 92 87 85 żetonów tutaj. Podziękowania dla Petera Taylora za zmniejszenie 7 tokenów.

Victor Stafusa
źródło
1
Możesz zapisać 3 tokeny, przechowując numer wersji w s1. Prawdopodobnie możesz uratować kolejne 2, łapiąc bezpośrednio Ognisty, przy założeniu, że DatatypeConfigurationExceptionnie zostanie rzucony.
Peter Taylor,
1
Lub lepiej, zachowaj, int aale zainicjuj go natychmiast, aby ifblok stał się pusty. Neguj warunek, usuń pozostałe i użyj --zamiast bezpośredniego przypisania do a.
Peter Taylor,