9
$\begingroup$

This is based on a very unusual behaviour of Java I recently found while implementing a personal project. It requires a very good understanding of Java and some knowledge of its standard library, and might even need some digging in the JLS or even JVMS.


Usually in Java, adding a cast can only fix compile-time problems. At runtime, adding a cast can only result in more exceptions (namely ClassCastExceptions), not less. However, there is an exception no pun intended to this rule, and it's your job to find it.

Concretely, implement the method receiver in the following Java program, such that the program compiles and prints hello without throwing any (uncaught) exceptions. When removing or commenting out the cast to String in the main method (without making any other changes), the program should still compile, but crash with an uncaught exception and not print anything except the exception's stack trace.

public class CastNecessaryAtRuntimePuzzle
{
    public static void main(String[] args) throws Exception
    {
        handle((String) (ChangesAllowed.receiver().get()));
    }

    static void handle(Object value)
    {
        System.out.println(value);
    }

    class ChangesAllowed
    {
        static AnyReturnType receiver() throws Exception
        {
            //TODO
        }
    }
}

The fact that the casted expression is parenthesized is not relevant, it's just to make precedence clear. Also, access modifiers (public, private) aren't relevant for solving this.

This has been tested on Java 19, but should work on later versions as well. For now, I won't reveal the minimum Java version required, because it might be a hint. (EDIT: The minimum Java version is now revealed in one of the hints.)

Rules:

  • You may add your own imports.
  • Apart from imports, you may only change the body of the class ChangesAllowed. This includes the return type and body of the receiver() method.
  • You may create own classes as inner classes of ChangesAllowed. Anonymous classes are allowed as well, but obviously only in methods in ChangesAllowed or its nested classes.

There are no tricks. This is not a challenge to find the loophole; this challenge is really solvable the way it is posed. Some examples of forbidden loopholes follow:

  • Your solution may not use any kind of introspection like reading the program's source code (or class file) to find out whether the cast exists or not, and act differently according to that information.
  • The main method given here must be the main method invoked by the JVM; you may not add your own main method and invoke that instead.
  • Your solution may not depend on bugs - neither in the compiler, nor in the JVM.
  • Your solution may not use Unicode magic to cause parts of the program outside of ChangesAllowed to change - this includes things like RTL override attacks etc.
  • Your solution may not use sun.misc.Unsafe or jdk.internal.misc.Unsafe.
  • Similarily, you may not use java agents, the JVM's debug interface or similar. (The reason fo this is that if this would be allowed, this could be used in various ways: to cause a different version of the given main method to be executed, or create a subclass of String, or change the class String in any way.)
  • Your solution may not depend on a certain JVM implementation - especially not your own, custom JVM written for this puzzle. This has to work at least on Oracle's HotSpot JVM.
  • Your solution may not depend on certain JVM options, especially not -XX:UnlockDiagnosticVMOptions. On the HotSpot JVM, this has to work without any extra options.
  • Your solution may not use any tricks to get an object of one type into a variable of an incompatible type - this would be possible with Unsafe or JVM bugs, but neither are allowed.

Hints:

As far as I can tell, this should be possible since Java 9 16.

Finding the correct return type is almost all of the work - if you know the return type, creating a usable value of that type is kind of straightforward.

The name of the method get called on the result of receiver() is very important! In fact, there's only a handful of method names which I could have used instead of get. The challenge is not solvable with any other method name! (Concretely, there are 5 names which don't require other changes, and further 27 method names which would require some more changes to the challenge, if I counted correctly.)

No generics whatsoever are involved in the solution.

The thrown exception won't be a ClassCastException - howver, I don't yet reveal the real exception type because it would make the challenge very easy.

There is exactly one correct return type, and that is a class from the JRE - specifically, the java.base module. This class is the only return type with which this challenge can be solved - it's not possible to solve this with an own class as the return type.


If this kind of puzzle isn't welcome here, feel free to close as off-topic. However, it seems to fit worse on codegolf.stackexchange.com: That community frowns upon language-specific challenges or challenges where people can't compete, and this puzzle is both. Also, stackoverflow.com isn't meant for puzzles either - they recommend puzzling.stackexchange.com instead.

$\endgroup$
9
  • $\begingroup$ This one is damn tricky :P $\endgroup$
    – Avi
    Sep 2, 2023 at 1:14
  • 1
    $\begingroup$ I love computer puzzles, but this one seems a little too specific to the Java language, so not sure if it fits in this community. $\endgroup$ Sep 2, 2023 at 14:25
  • $\begingroup$ This has to be something about overloaded methods which are only distinguished by their return type, and the cast on the method call forces the compiler to choose one overload where it would choose another overload if the cast were not present. $\endgroup$ Sep 3, 2023 at 8:41
  • $\begingroup$ @codewarrior0 Bonus hint: This is not about overloaded methods - in fact, according to the JLS, what happens with the return value of a method will not be used in determining which method is to be invoked: When a method is invoked, the number of actual arguments [...] and the compile-time types of the arguments are used, at compile time, to determine the signature of the method that will be invoked., from Java 20's JLS 8.4.9: docs.oracle.com/javase/specs/jls/se20/html/jls-8.html#jls-8.4.9 $\endgroup$ Sep 3, 2023 at 23:26
  • 1
    $\begingroup$ No, it has to be a certain class from the JRE - that's what I was trying to say with the last hint. I'll edit it for clarity. $\endgroup$ Sep 4, 2023 at 2:36

1 Answer 1

9
$\begingroup$

A (the?) solution:

import java.lang.invoke.*;

public class CastNecessaryAtRuntimePuzzle
{
    public static void main(String[] args) throws Exception
    {
        handle((String) (ChangesAllowed.receiver().get()));
    }

    static void handle(Object value)
    {
        System.out.println(value);
    }

    class ChangesAllowed
    {
        static String s = "Hello, World!";

        static VarHandle receiver() throws Exception
        {
            return MethodHandles.lookup().findStaticVarHandle(ChangesAllowed.class, "s", String.class).withInvokeExactBehavior();
        }
    }
}

Comments:

VarHandle's get (and other methods of VarHandle and MethodHandle) are very special methods called "signature polymorphic methods" (see e.g. here in JLS and here in JVMS). As I understand, this basically means the compiler will determine the argument and return types of the call to get based on the context where it is called. Hence, the cast to String will make it compile a call to a method with return type String whereas if there is no cast it will compile a call to a method with return type Object. The VarHandle I return is bound to the static String variable s. Normally, the system is permissive enough that this will still work with or without the cast, but withInvokeExactBehavior forces the signature to match exactly which, I guess, in the case of a VarHandle means the return type of get has to exactly match the bound field's type. Otherwise, it will throw WrongMethodTypeException. However, note that withInvokeExactBehavior seems to be from Java 16, so maybe this is not what OP was thinking of. I'm not sure how to get an error from VarHandle otherwise. (But, a construction with MethodHandle is probably possible of making a similarly meaningful cast much earlier, but may not lend itself to this kind of puzzle presentation.) (godbolt can run Java 20 online.)

$\endgroup$
3
  • $\begingroup$ Yep, that's the exactly the solution I had in mind! And I totally missed that <the thing> is from Java 16; I'll edit the question. $\endgroup$ Sep 4, 2023 at 9:36
  • $\begingroup$ By the way, how did you find this? $\endgroup$ Sep 4, 2023 at 9:40
  • 2
    $\begingroup$ I was thinking about this for a while, but didn't find/notice anything about signature polymorphism or this type, but after the last hints, I was looking at this list for methods of the right parameters. That sounds like I would find the type easily but somehow I followed the link from here instead. Never seen it before so it took a bit to figure out what it was all about, but it quickly looked promising. $\endgroup$
    – tehtmi
    Sep 4, 2023 at 14:56

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.