RFR: 8329420: Java 22 (and 23) launcher calls default constructor although main() is static
Alan Bateman
alanb at openjdk.org
Sun Apr 14 06:54:49 UTC 2024
On Fri, 12 Apr 2024 10:16:36 GMT, Jan Lahoda <jlahoda at openjdk.org> wrote:
> Consider code like:
>
> public class MainClass {
> public MainClass() {
> System.out.println("Constructor called!");
> }
> public static void main() {
> System.out.println("main called!");
> }
> }
>
> and compile and run it, with preview enabled, like:
>
> $ javac /tmp/MainClass.java
> $ java --enable-preview -classpath /tmp MainClass
> Constructor called!
> main called!
>
>
> That is wrong, as the `main` method is static, and there is no need to create a new instance of the class.
>
> The reason is that as launcher attempts to invoke the main method, it goes in the following order: 1) static-main-with-args; 2) instance-main-with-args; 3) static-main-without-args; 4) instance-main-without-args. But, for the instance variants, the code first creates a new instance of the given class, and only then attempts to lookup the `main` method, and will pass to the next option when the `main` method lookup fails. So, when invoking static-main-without-args, the current class instance may be created for instance-main-with-args, which will then fail due to the missing `main(String[])` method.
>
> The proposed solution to this problem is to simply first do a lookup for the `main` method (skipping to the next variant when the given main method does not exist, without instantiating the current class).
>
> There is also a relatively closely related problem: what happens when the constructor throws an exception?
>
> public class MainClass {
> public MainClass() {
> if (true) throw new RuntimeException();
> }
> public void main() {
> System.out.println("main called!");
> }
> }
>
>
> when compiled an run, this produces no output whatsoever:
>
> $ javac /tmp/MainClass.java
> $ java --enable-preview -classpath /tmp MainClass
> $
>
>
> This is because any exceptions thrown from the constructor are effectively ignored, and the launcher will continue with the next variant. This seems wrong - the exception should be printed for the user, like:
>
> $ java --enable-preview -classpath /tmp/ MainClass
> Exception in thread "main" java.lang.RuntimeException
> at MainClass.<init>(MainClass.java:3)
>
>
> This patch proposes to do that by not consuming the exceptions thrown from the constructor, and stop the propagation to the next variant.
src/java.base/share/native/libjli/java.c line 434:
> 432: CHECK_EXCEPTION_FAIL();
> 433: jobject mainObject = (*env)->NewObject(env, mainClass, constructor);
> 434: CHECK_EXCEPTION_NULL_PASS(mainObject);
There will be a pending exception if NewObject returns NULL. I think it would be a lot easier to read if this were to just check if mainObject is NULL and return 1. Same comment for the other usage in invokeInstanceMainWithoutArgs. That would avoid CHECK_EXCEPTION_NULL_PASS which is very confusing to see at the use-sites.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/18753#discussion_r1564512331
More information about the core-libs-dev
mailing list