16
\$\begingroup\$

The objective is to write java code that detects the JVM version relying in compatibility changes, side effects, bugs and/or undefined behavior that works in a way in one version and another way in another version. Further, the code should be at least a bit readable, without sacrificing whitespaces and legible variable names.

To ensure that objective, the exact formal rules are:

  1. The code must be written in java and should output the JRE version in which it is running.

  2. The code must not use any JDK or JRE API provided specifically for detecting the java version or which gives the JDK or JRE version for free.

  3. The code must not use reflection.

  4. The code is only required to work in Hotspot Java SE 5, 6 and 7, but may work in other JVMs.

  5. The code must not use any third-party libraries in the classpath.

  6. The code must not start any other process, java or not.

  7. The code must not use environment variables.

  8. The code must not search the file system looking for pre-existing files or folders.

  9. The code must be contained in a single file and be called via public static void main(String[] args) or public static void main(String... args).

  10. The code must not use any non-public API present in the JRE.

  11. The code must not generate any NoClassDefFoundError, NoSuchMethodError, ClassNotFoundException or NoSuchMethodException during its execution.

  12. The code should run in a system disconnected from the internet or from any local network.

  13. You should provide an explanation of why it behaves in one way in a version and another way in another version.

Scoring

The method used for measuring the best solution is max(n/s), where n is the number of different java versions detected without violating any of these rules (at least versions 5, 6 and 7) and s is the number of lexical tokens in the solution.

\$\endgroup\$
5
  • \$\begingroup\$ Could not find a better tag, and I had to provide at last two. Further, I do not have enough rep to create new tags. The reason for java is because it is supposingly a very portable language, so writing that would be very interesting. Further, the java versions are defined in a way that we can compare entries detecting the environment with uniformity, without ending to have to compare oranges to apples. \$\endgroup\$ Commented Dec 1, 2012 at 21:55
  • \$\begingroup\$ You could consider [underhanded] arguing that VM version detection is a step in attacking the system. I can't say that I have another suggestion. \$\endgroup\$ Commented Dec 1, 2012 at 22:05
  • \$\begingroup\$ @dmckee Dropped the [code-golf] tag. Add the [underhanded] tag. Could you please create the [java] tag? \$\endgroup\$ Commented Dec 1, 2012 at 23:04
  • 4
    \$\begingroup\$ I'm voting to close this question as off-topic because underhanded challenges are no longer on-topic on this site. meta.codegolf.stackexchange.com/a/8326/20469 \$\endgroup\$
    – cat
    Commented Apr 19, 2016 at 2:22
  • \$\begingroup\$ @cat, you should instead have removed the tag, because it didn't fit the question. \$\endgroup\$ Commented Aug 16, 2017 at 7:16

3 Answers 3

22
\$\begingroup\$

I'm not sure what my score is, because it depends on what precisely you consider to be a lexical token, but I'm trying to abuse that counting system as much as possible with a long string...

It also depends on whether you count this as identifying 7 different versions or 16... (It could trivially be extended up to 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){}
    }
}

It works by attempting to define an interface in a custom classloader with descending major version numbers of the class format. The first one which doesn't throw a java.lang.UnsupportedClassVersionError corresponds to the VM's version.

\$\endgroup\$
5
  • \$\begingroup\$ Counted 84 tokens. Still did not tested it though. \$\endgroup\$ Commented Dec 1, 2012 at 22:02
  • \$\begingroup\$ Your answer is genial. Could trivially reduce to 83 tokens using String... args. \$\endgroup\$ Commented Dec 1, 2012 at 22:51
  • \$\begingroup\$ @Victor, that would complicate the question of whether it supports 7 different versions even more. I'm not aware of any compiler which supports Java 5 syntax and compiles to Java 1-compatible class files. \$\endgroup\$ Commented Dec 1, 2012 at 23:14
  • \$\begingroup\$ Good point. I forgot about that. \$\endgroup\$ Commented Dec 1, 2012 at 23:16
  • 1
    \$\begingroup\$ Java 1.1.8 (in MRJ 2.2.6) failed to compile this, until I added 17 more tokens: protected Class loadClass(String name, boolean resolve) { return Object.class; }. The current API docs neglect to mention how this was an abstract method before Java 1.2. I return Object.class because the method gets one call for "java.lang.Object". \$\endgroup\$
    – kernigh
    Commented Jun 28, 2014 at 2:58
9
\$\begingroup\$

6/102 = 0.0588

Detects 6 versions. Has 102 lexical tokens (down from 103, after I deleted public in 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 introduced character encodings and cryptographic algorithms to Java. Later versions added more encodings and algorithms. This program tries to use encodings and algorithms until it catches an exception. I expect a missing encoding to throw java.io.UnsupportedEncodingException and a missing algorithm to throw java.security.NoSuchAlgorithmException.

I had an old PowerPC Macintosh with four old versions of Java. My OpenBSD machine has two more versions, so I tested these six versions:

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

This program can also run in JamVM 1.5.4 and gcj 4.8.2 for OpenBSD, but does not identify them as different implementations. It only prints "Java 5".

Mac OS Runtime for Java

Thanks to "Write once, run everywhere!", I may write this program once, compile it once, and run one GuessVersion.class in all eight virtual machines. I need a compiler for Java 1.1, the oldest version in my collection.

My compiler is the javac tool from MRJ SDK 2.2. Because the Classic Mac OS had no command line, javac is a pretty graphical tool where I select files and options and click "Do Javac". After I edit my code, I just click "Do Javac" again.

javac from MRJ SDK 2.2 for Classic Mac OS

The easiest way to run GuessVersion.class is to open it in JBindery, another tool from MRJ SDK 2.2. The runtime is MRJ 2.2.6, an implementation of Java 1.1.8.

\$\endgroup\$
8
\$\begingroup\$
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);
    }
}

The interning algorithm changed between Java 6 and 7. See https://stackoverflow.com/a/7224864/540552

XMLGregorianCalendar.equals(null) used to throw NullPointerException in java 5, but this was fixed in java 6. See https://bugs.java.com/bugdatabase/view_bug?bug_id=6285370

100 96 92 87 85 tokens here. Thanks to Peter Taylor for reducing 7 tokens.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You can save 3 tokens by storing the version number in s1. You can probably save a further 2 by catching Throwable directly, on the assumption that DatatypeConfigurationException won't be thrown. \$\endgroup\$ Commented Dec 1, 2012 at 23:17
  • 1
    \$\begingroup\$ Or better, keep int a but initialise it immediately, so the if block becomes empty. Negate the condition, remove the else, and use -- instead of direct assignment to a. \$\endgroup\$ Commented Dec 1, 2012 at 23:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.