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 ClassCastException
s), 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 thereceiver()
method. - You may create own classes as inner classes of
ChangesAllowed
. Anonymous classes are allowed as well, but obviously only in methods inChangesAllowed
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
orjdk.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 ofString
, or change the classString
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
916.
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 ofreceiver()
is very important! In fact, there's only a handful of method names which I could have used instead ofget
. 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.
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$