diff --git a/src/java.base/share/classes/jdk/internal/misc/MethodFinder.java b/src/java.base/share/classes/jdk/internal/misc/MethodFinder.java index 60895b8115a..1ee608f2caf 100644 --- a/src/java.base/share/classes/jdk/internal/misc/MethodFinder.java +++ b/src/java.base/share/classes/jdk/internal/misc/MethodFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ package jdk.internal.misc; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Objects; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; @@ -88,21 +89,27 @@ public class MethodFinder { mainMethod = JLA.findMethod(cls, false, "main", String[].class); } - if (mainMethod == null || !isValidMainMethod(mainMethod)) { + if (mainMethod == null || !isValidMainMethod(cls, mainMethod)) { mainMethod = JLA.findMethod(cls, false, "main"); } - if (mainMethod == null || !isValidMainMethod(mainMethod)) { + if (mainMethod == null || !isValidMainMethod(cls, mainMethod)) { return null; } return mainMethod; } - private static boolean isValidMainMethod(Method mainMethodCandidate) { + private static boolean isValidMainMethod(Class initialClass, Method mainMethodCandidate) { return mainMethodCandidate.getReturnType() == void.class && - !Modifier.isPrivate(mainMethodCandidate.getModifiers()); - + !Modifier.isPrivate(mainMethodCandidate.getModifiers()) && + (Modifier.isPublic(mainMethodCandidate.getModifiers()) || + Modifier.isProtected(mainMethodCandidate.getModifiers()) || + isInSameRuntimePackage(initialClass, mainMethodCandidate.getDeclaringClass())); } + private static boolean isInSameRuntimePackage(Class c1, Class c2) { + return Objects.equals(c1.getPackageName(), c2.getPackageName()) && + c1.getClassLoader() == c2.getClassLoader(); + } } diff --git a/test/jdk/tools/launcher/InstanceMainTest.java b/test/jdk/tools/launcher/InstanceMainTest.java index 273d56a86bd..3a2f8fe90c1 100644 --- a/test/jdk/tools/launcher/InstanceMainTest.java +++ b/test/jdk/tools/launcher/InstanceMainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,11 +28,12 @@ import java.util.function.Consumer; /** * @test - * @bug 8329420 + * @bug 8329420 8377004 * @summary test execution priority and behavior of main methods * @run main/timeout=480 InstanceMainTest */ public class InstanceMainTest extends TestHelper { + private static String JAVA_VERSION = System.getProperty("java.specification.version"); private static final String[] SOURCES = new String[] { // static dominating with args @@ -227,11 +228,7 @@ public class InstanceMainTest extends TestHelper { private static void testExecutionOrder() throws Exception { for (TestCase testCase : EXECUTION_ORDER) { performTest(testCase.sourceCode, testCase.enablePreview(), tr -> { - if (!Objects.equals(testCase.expectedOutput, tr.testOutput)) { - throw new AssertionError("Unexpected output, " + - "expected: " + testCase.expectedOutput + - ", actual: " + tr.testOutput); - } + assertEquals(testCase.expectedOutput, tr.testOutput); }); } } @@ -350,20 +347,227 @@ public class InstanceMainTest extends TestHelper { private static void performTest(String source, boolean enablePreview, Consumer validator) throws Exception { Path mainClass = Path.of("MainClass.java"); Files.writeString(mainClass, source); - var version = System.getProperty("java.specification.version"); var previewRuntime = enablePreview ? "--enable-preview" : "-DtestNoPreview"; var previewCompile = enablePreview ? "--enable-preview" : "-XDtestNoPreview"; - var trSource = doExec(javaCmd, previewRuntime, "--source", version, "MainClass.java"); + var trSource = doExec(javaCmd, previewRuntime, "--source", JAVA_VERSION, "MainClass.java"); validator.accept(trSource); - compile(previewCompile, "--source", version, "MainClass.java"); + compile(previewCompile, "--source", JAVA_VERSION, "MainClass.java"); String cp = mainClass.toAbsolutePath().getParent().toString(); var trCompile = doExec(javaCmd, previewRuntime, "--class-path", cp, "MainClass"); validator.accept(trCompile); } + private static void testInheritance() throws Exception { + Path testInheritance = Path.of("testInheritance"); + Path src = testInheritance.resolve("src"); + Path classes = testInheritance.resolve("classes"); + Path mainClass = src.resolve("Main.java"); + Path libClass = src.resolve("p").resolve("Lib.java"); + + Files.createDirectories(libClass.getParent()); + + Files.writeString(mainClass, + """ + import p.Lib; + + public class Main extends Lib { + public void main() { + System.err.println("Main!"); + } + } + """); + + { + Files.writeString(libClass, + """ + package p; + public class Lib { + void main(String... args) { + System.err.println("Lib!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "Main"); + assertEquals(List.of("Main!"), tr.testOutput); + } + + { + Files.writeString(libClass, + """ + package p; + public class Lib { + protected void main(String... args) { + System.err.println("Lib!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "Main"); + assertEquals(List.of("Lib!"), tr.testOutput); + } + + { + Files.writeString(libClass, + """ + package p; + public class Lib { + public void main(String... args) { + System.err.println("Lib!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "Main"); + assertEquals(List.of("Lib!"), tr.testOutput); + } + + { + Files.writeString(mainClass, + """ + package p; + + public class Main extends Lib { + public void main() { + System.err.println("Main!"); + } + } + """); + + Files.writeString(libClass, + """ + package p; + public class Lib { + void main(String... args) { + System.err.println("Lib!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "p.Main"); + assertEquals(List.of("Lib!"), tr.testOutput); + } + + { + Files.writeString(mainClass, + """ + package p; + + public class Main implements Lib { + public void main() { + System.err.println("Main!"); + } + } + """); + + Files.writeString(libClass, + """ + package p; + public interface Lib { + public default void main(String... args) { + System.err.println("Lib!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "p.Main"); + assertEquals(List.of("Lib!"), tr.testOutput); + } + + { + Files.writeString(mainClass, + """ + package p; + + public class Main implements Lib { + public void main() { + System.err.println("Main!"); + } + } + """); + + Files.writeString(libClass, + """ + package p; + public interface Lib { + public static void main(String... args) { + System.err.println("Lib!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "p.Main"); + assertEquals(List.of("Main!"), tr.testOutput); + } + + { + Files.writeString(mainClass, + """ + package p; + + public class Main extends AbstractClass implements Lib { + } + abstract class AbstractClass { + public void main(String... args) { + System.err.println("Correct."); + } + } + """); + + Files.writeString(libClass, + """ + package p; + public interface Lib { + default void main(String... args) { + System.err.println("Incorrect!"); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "p.Main"); + assertEquals(List.of("Correct."), tr.testOutput); + } + + { + Files.writeString(mainClass, + """ + package p; + + public class Main extends AbstractClass implements Lib { + } + abstract class AbstractClass { + public void main() { + System.err.println("Incorrect!"); + } + } + """); + + Files.writeString(libClass, + """ + package p; + public interface Lib { + default void main(String... args) { + System.err.println("Correct."); + } + } + """); + compile("--release", JAVA_VERSION, "-d", classes.toString(), mainClass.toString(), libClass.toString()); + var tr = doExec(javaCmd, "--class-path", classes.toString(), "p.Main"); + assertEquals(List.of("Correct."), tr.testOutput); + } + } + + private static void assertEquals(List expected, List actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Unexpected output, " + + "expected: " + expected + + ", actual: " + actual); + } + } public static void main(String... args) throws Exception { testMethodOrder(); testExecutionOrder(); testExecutionErrors(); + testInheritance(); } } diff --git a/test/jdk/tools/launcher/MethodFinderTest.java b/test/jdk/tools/launcher/MethodFinderTest.java new file mode 100644 index 00000000000..6e1e68d8225 --- /dev/null +++ b/test/jdk/tools/launcher/MethodFinderTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8377004 + * @summary Whitebox test for MethodFinder + * @modules java.base/jdk.internal.misc + * jdk.compiler + * jdk.zipfs + * @run junit MethodFinderTest + */ + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import jdk.internal.misc.MethodFinder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MethodFinderTest { + + private static final String JAVA_VERSION = System.getProperty("java.specification.version"); + + @Test + public void testDistinctClassLoaders() throws Exception { + Path base = Path.of("testDistinctClassLoaders"); + Path libSrc = base.resolve("libSrc"); + Path libClasses = base.resolve("libClasses"); + Path libJava = libSrc.resolve("p").resolve("Lib.java"); + + Files.createDirectories(libJava.getParent()); + + Files.writeString(libJava, + """ + package p; + public class Lib { + void main(String... args) { + System.err.println("Lib!"); + } + } + """); + + TestHelper.compile("--release", JAVA_VERSION, "-d", libClasses.toString(), libJava.toString()); + + Path mainSrc = base.resolve("mainSrc"); + Path mainClasses = base.resolve("mainClasses"); + Path mainJava = mainSrc.resolve("p").resolve("Main.java"); + + Files.createDirectories(mainJava.getParent()); + + Files.writeString(mainJava, + """ + package p; + + public class Main extends Lib { + public void main() { + System.err.println("Main!"); + } + } + """); + + TestHelper.compile("--release", JAVA_VERSION, "--class-path", libClasses.toString(), "-d", mainClasses.toString(), mainJava.toString()); + + { + ClassLoader cl = new URLClassLoader(new URL[] { + libClasses.toUri().toURL(), + mainClasses.toUri().toURL() + }); + Class mainClass = cl.loadClass("p.Main"); + Method mainMethod = MethodFinder.findMainMethod(mainClass); + + //p.Main and p.Lib are in the same runtime package: + assertEquals("p.Lib", mainMethod.getDeclaringClass().getName()); + } + + { + ClassLoader libCl = new URLClassLoader(new URL[] { + libClasses.toUri().toURL(), + }); + ClassLoader mainCl = new URLClassLoader(new URL[] { + mainClasses.toUri().toURL() + }, libCl); + Class mainClass = mainCl.loadClass("p.Main"); + Method mainMethod = MethodFinder.findMainMethod(mainClass); + + //p.Main and p.Lib are in the different runtime packages: + assertEquals("p.Main", mainMethod.getDeclaringClass().getName()); + } + } + + @Test + public void testWrongEquals() throws Exception { + Path base = Path.of("testDistinctClassLoaders"); + Path libSrc = base.resolve("libSrc"); + Path libClasses = base.resolve("libClasses"); + Path libJava = libSrc.resolve("p").resolve("Lib.java"); + + Files.createDirectories(libJava.getParent()); + + Files.writeString(libJava, + """ + package p; + public class Lib { + void main(String... args) { + System.err.println("Lib!"); + } + } + """); + + TestHelper.compile("--release", JAVA_VERSION, "-d", libClasses.toString(), libJava.toString()); + + Path mainSrc = base.resolve("mainSrc"); + Path mainClasses = base.resolve("mainClasses"); + Path mainJava = mainSrc.resolve("p").resolve("Main.java"); + + Files.createDirectories(mainJava.getParent()); + + Files.writeString(mainJava, + """ + package p; + + public class Main extends Lib { + public void main() { + System.err.println("Main!"); + } + } + """); + + TestHelper.compile("--release", JAVA_VERSION, "--class-path", libClasses.toString(), "-d", mainClasses.toString(), mainJava.toString()); + + { + ClassLoader cl = new URLClassLoader(new URL[] { + libClasses.toUri().toURL(), + mainClasses.toUri().toURL() + }); + Class mainClass = cl.loadClass("p.Main"); + Method mainMethod = MethodFinder.findMainMethod(mainClass); + + //p.Main and p.Lib are in the same runtime package: + assertEquals("p.Lib", mainMethod.getDeclaringClass().getName()); + } + + { + class WrongEquals extends URLClassLoader { + + public WrongEquals(URL[] urls) { + super(urls); + } + + public WrongEquals(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof WrongEquals; + } + } + ClassLoader libCl = new WrongEquals(new URL[] { + libClasses.toUri().toURL(), + }); + ClassLoader mainCl = new WrongEquals(new URL[] { + mainClasses.toUri().toURL() + }, libCl); + Class mainClass = mainCl.loadClass("p.Main"); + Method mainMethod = MethodFinder.findMainMethod(mainClass); + + //p.Main and p.Lib are in the different runtime packages: + assertEquals("p.Main", mainMethod.getDeclaringClass().getName()); + } + } +}