编写Java代码以检测JVM版本


17

目的是编写Java代码来检测JVM版本,该代码依赖于兼容性更改,副作用,错误和/或未定义行为,这些行为在一个版本中以某种方式起作用,而在另一版本中以另一种方式起作用。此外,该代码应至少具有一点可读性,而不会牺牲空格和清晰的变量名。

为了确保这一目标,确切的正式规则是:

  1. 该代码必须用Java编写,并输出运行它的JRE版本。

  2. 该代码不得使用任何专门用于检测Java版本或免费提供JDK或JRE版本的JDK或JRE API。

  3. 该代码不得使用反射。

  4. 该代码仅在Hotspot Java SE 5、6和7中才需要,但在其他JVM中也可以。

  5. 该代码不得在类路径中使用任何第三方库。

  6. 该代码不得启动任何其他进程,无论是否是Java。

  7. 该代码不得使用环境变量。

  8. 该代码不得搜索文件系统以查找预先存在的文件或文件夹。

  9. 该代码必须包含在一个文件中,并通过public static void main(String[] args)或调用public static void main(String... args)

  10. 该代码不得使用JRE中存在的任何非公共API。

  11. 该代码在执行期间不得生成任何NoClassDefFoundError,NoSuchMethodError,ClassNotFoundException或NoSuchMethodException。

  12. 该代码应在与Internet或任何本地网络断开连接的系统中运行。

  13. 您应该提供一个解释,说明为什么它在一个版本中会以一种方式运行,而在另一个版本中会以另一种方式运行。

计分

衡量最佳解决方案的方法是max(n / s),其中n是在不违反任何这些规则的情况下检测到的不同Java版本的数量(至少是版本5、6和7),而s是词汇标记的数量在解决方案中。


找不到更好的标签,我不得不提供最后两个。此外,我没有足够的代表来创建新标签。使用Java的原因是因为它应该是一种可移植的语言,因此编写该语言将非常有趣。此外,java版本的定义方式是,我们可以比较均匀地检测环境的条目,而不必将桔子与苹果进行比较。
维克多·斯塔夫萨

您可以考虑[不足]争论说,VM版本检测是攻击系统的一步。我不能说我有另一个建议。
dmckee

@dmckee删除了[code-golf]标签。添加[underhanded]标签。您可以创建[java]标签吗?
Victor Stafusa

4
我投票结束这个问题是不合时宜的,因为轻率的挑战不再是这个网站上的话题。meta.codegolf.stackexchange.com/a/8326/20469

@cat,您应该删除该标签,因为它不适合这个问题。
彼得·泰勒

Answers:


9

6/102 = 0.0588

检测6个版本。具有102个词法标记(来自103下来,我删除后publicpublic 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.1Java引入了字符编码和加密算法。更高版本增加了更多的编码和算法。该程序尝试使用编码和算法,直到捕获异常。我期望抛出丢失的编码和抛出java.io.UnsupportedEncodingException丢失的算法java.security.NoSuchAlgorithmException

我有一台带有四个Java旧版本的PowerPC Macintosh。我的OpenBSD机器还有两个版本,因此我测试了这六个版本:

  • 适用于Mac OS 9.2.2的MRJ 2.2.6中的Java 1.1.8
  • 适用于Mac OS X Panther的Java 1.3.1_16
  • 适用于Mac OS X Tiger的Java 1.4.2_21
  • 适用于Mac OS X Tiger的Java 1.5.0_19
  • 适用于OpenBSD 5.5的OpenJDK 1.6.0_32
  • 适用于OpenBSD 5.5的OpenJDK 1.7.0_21

该程序也可以在OpenBSD的JamVM 1.5.4和gcj 4.8.2中运行,但是不能将它们标识为不同的实现。它仅打印“ Java 5”。

Mac OS Java运行时

感谢“编写一次,到处运行!”,我可以编写一次该程序,编译一次,然后在所有八个虚拟机中运行一个GuessVersion.class。我需要Java 1.1的编译器,这是我收藏中最旧的版本。

我的编译器是javacMRJ SDK 2.2中的工具。因为Classic Mac OS没有命令行,所以它javac是一个漂亮的图形工具,可以在其中选择文件和选项,然后单击“执行Javac”。编辑代码后,只需再次单击“执行Javac”。

适用于经典Mac OS的MRJ SDK 2.2中的javac

运行GuessVersion.class的最简单方法是在MRJ SDK 2.2中的另一个工具JBindery中打开它。运行时是MRJ 2.2.6,它是Java 1.1.8的实现。


22

我不确定我的分数是多少,因为它取决于您确切地认为是词法标记的原因,但是我正尝试尽可能多地使用长字符串来滥用该计数系统...

它还取决于您是否将其视为可识别7个不同版本或16个版本(它可以扩展到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){}
    }
}

它通过尝试在自定义类加载器中定义类格式为主要版本号降序的接口来工作。第一个不抛出的java.lang.UnsupportedClassVersionError对应于VM的版本。


计数了84个代币。仍然没有测试。
维克多·斯塔夫萨

您的回答很温和。可以使用减少到83个令牌String... args
维克多·斯塔夫萨

@Victor,这将使它是否支持7种不同版本的问题更加复杂。我不知道任何支持Java 5语法并将其编译为与Java 1兼容的类文件的编译器。
彼得·泰勒

好点子。我忘记了。
维克多·斯塔夫萨

1
Java 1.1.8(在MRJ 2.2.6中)未能对此进行编译,直到我又添加了17个令牌:protected Class loadClass(String name, boolean resolve) { return Object.class; }。当前的API文档忽略了在Java 1.2之前这是如何抽象的方法。我返回Object.class,因为该方法获得了对“ 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);
    }
}

实习算法在Java 6和7之间进行了更改。请参阅/programming//a/7224864/540552

XMLGregorianCalendar.equals(null)用于在Java 5中引发NullPointerException,但在Java 6中已修复。请参见http://bugs.sun.com/view_bug.do?bug_id=6285370

100 96 92 87 85个令牌。感谢Peter Taylor减少了7个令牌。


1
您可以通过将版本号存储在s1中来保存3个令牌。您可以通过直接捕获Throwable来节省另外2个对象,但前提是DatatypeConfigurationException不会被抛出。
彼得·泰勒

1
或者更好的int a做法是,保留但立即对其进行初始化,以便该if块变为空。否定条件,删除else,然后使用--代替直接分配给a
彼得·泰勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.