Escreva um código java para detectar a versão da JVM

17

O objetivo é escrever código java que detecta a versão da JVM confiando em alterações de compatibilidade, efeitos colaterais, bugs e / ou comportamento indefinido que funcionam de uma maneira em uma versão e outra em outra versão. Além disso, o código deve ser pelo menos um pouco legível, sem sacrificar espaços em branco e nomes de variáveis ​​legíveis.

Para garantir esse objetivo, as regras formais exatas são:

  1. O código deve ser escrito em java e deve gerar a versão do JRE na qual está sendo executado.

  2. O código não deve usar nenhuma API JDK ou JRE fornecida especificamente para detectar a versão java ou que fornece a versão JDK ou JRE gratuitamente.

  3. O código não deve usar reflexão.

  4. O código é necessário apenas para funcionar no Hotspot Java SE 5, 6 e 7, mas pode funcionar em outras JVMs.

  5. O código não deve usar nenhuma biblioteca de terceiros no caminho de classe.

  6. O código não deve iniciar nenhum outro processo, java ou não.

  7. O código não deve usar variáveis ​​de ambiente.

  8. O código não deve procurar no sistema de arquivos procurando arquivos ou pastas preexistentes.

  9. O código deve estar contido em um único arquivo e ser chamado via public static void main(String[] args)ou public static void main(String... args).

  10. O código não deve usar nenhuma API não pública presente no JRE.

  11. O código não deve gerar nenhum NoClassDefFoundError, NoSuchMethodError, ClassNotFoundException ou NoSuchMethodException durante sua execução.

  12. O código deve ser executado em um sistema desconectado da Internet ou de qualquer rede local.

  13. Você deve fornecer uma explicação de por que ele se comporta de uma maneira em uma versão e de outra maneira em outra versão.

Pontuação

O método usado para medir a melhor solução é max (n / s), em que n é o número de versões java diferentes detectadas sem violar nenhuma dessas regras (pelo menos as versões 5, 6 e 7) e s é o número de tokens lexicais na solução.

Victor Stafusa
fonte
Não foi possível encontrar uma tag melhor, e eu tive que fornecer as duas últimas. Além disso, não tenho representante suficiente para criar novas tags. A razão para o java é porque supostamente é uma linguagem muito portátil, portanto, escrever isso seria muito interessante. Além disso, as versões java são definidas de uma maneira que podemos comparar entradas que detectam o ambiente com uniformidade, sem precisar finalizar a comparação de laranjas com maçãs.
Victor Stafusa
Você pode considerar [disfarçado] argumentar que a detecção da versão da VM é uma etapa no ataque ao sistema. Não posso dizer que tenho outra sugestão.
dmckee
@dmckee Soltou a tag [code-golf]. Adicione a tag [underhanded]. Você poderia criar a tag [java]?
Victor Stafusa
4
Estou votando para encerrar esta questão como fora de tópico, porque os desafios secretos não estão mais no tópico neste site. Meta.codegolf.stackexchange.com/a/8326/20469
cat
@cat, você deveria ter removido a tag, porque ela não se encaixava na pergunta.
Peter Taylor

Respostas:

9

6/102 = 0,0588

Detecta 6 versões. Tem 102 símbolos lexicais (para baixo de 103, depois de ter suprimido publicem 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);
        }
}

O Java 1.1 introduziu codificações de caracteres e algoritmos criptográficos no Java. Versões posteriores adicionaram mais codificações e algoritmos. Este programa tenta usar codificações e algoritmos até capturar uma exceção. Espero que uma codificação ausente seja lançada java.io.UnsupportedEncodingExceptione um algoritmo ausente seja lançado java.security.NoSuchAlgorithmException.

Eu tinha um Macintosh PowerPC antigo com quatro versões antigas de Java. Minha máquina OpenBSD tem mais duas versões, então testei estas seis versões:

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

Este programa também pode ser executado no JamVM 1.5.4 e gcj 4.8.2 para OpenBSD, mas não os identifica como implementações diferentes. Ele imprime apenas "Java 5".

Tempo de execução do Mac OS para Java

Graças a "Escreva uma vez, execute em qualquer lugar!", Posso escrever este programa uma vez, compilá-lo uma vez e executar uma GuessVersion.class nas oito máquinas virtuais. Eu preciso de um compilador para Java 1.1, a versão mais antiga da minha coleção.

Meu compilador é a javacferramenta do MRJ SDK 2.2. Como o Mac OS clássico não tinha linha de comando, javacé uma ferramenta gráfica bastante bonita, onde seleciono arquivos e opções e clico em "Do Javac". Depois de editar meu código, basta clicar em "Do Javac" novamente.

javac do MRJ SDK 2.2 para Mac OS clássico

A maneira mais fácil de executar o GuessVersion.class é abri-lo no JBindery, outra ferramenta do MRJ SDK 2.2. O tempo de execução é MRJ 2.2.6, uma implementação do Java 1.1.8.

Kernigh
fonte
22

Não tenho certeza de qual é minha pontuação, porque depende do que você considera um símbolo lexical, mas estou tentando abusar do sistema de contagem o máximo possível com uma longa sequência ...

Também depende se você conta isso como identificando 7 versões diferentes ou 16 ... (Poderia ser estendido trivialmente até 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){}
    }
}

Ele funciona ao tentar definir uma interface em um carregador de classe personalizado com números de versão principais decrescentes do formato de classe. O primeiro que não lança um java.lang.UnsupportedClassVersionErrorcorresponde à versão da VM.

Peter Taylor
fonte
Contou 84 fichas. Ainda não o testou.
Victor Stafusa
Sua resposta é genial. Poderia reduzir trivialmente para 83 tokens usando String... args.
Victor Stafusa
@ Victor, isso complicaria a questão de saber se ele suporta 7 versões diferentes ainda mais. Não conheço nenhum compilador que suporte a sintaxe Java 5 e compile em arquivos de classe compatíveis com Java 1.
Peter Taylor
Bom ponto. Eu esqueci disso.
Victor Stafusa
11
Java 1.1.8 (em MRJ 2.2.6) não conseguiu compilar este, até que eu adicionei mais 17 fichas: protected Class loadClass(String name, boolean resolve) { return Object.class; }. Os documentos atuais da API não mencionam como esse foi um método abstrato antes do Java 1.2. Retorno Object.class porque o método recebe uma chamada para "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);
    }
}

O algoritmo de internação mudou entre Java 6 e 7. Consulte /programming//a/7224864/540552

XMLGregorianCalendar.equals (null) usado para lançar NullPointerException no java 5, mas isso foi corrigido no java 6. Consulte http://bugs.sun.com/view_bug.do?bug_id=6285370

100 96 92 87 85 tokens aqui. Agradecemos a Peter Taylor por reduzir 7 tokens.

Victor Stafusa
fonte
11
Você pode salvar 3 tokens armazenando o número da versão em s1. Provavelmente, você pode salvar mais 2 pegando Throwable diretamente, supondo que DatatypeConfigurationExceptionnão será lançado.
Peter Taylor
11
Ou melhor, mantenha int amas inicialize imediatamente, para que o ifbloco fique vazio. Negue a condição, remova o else e use em --vez da atribuição direta paraa .
Peter Taylor