8377004: Java Launcher incorrectly allows inheriting a package-private main from another package

Reviewed-by: jpai, alanb
This commit is contained in:
Jan Lahoda 2026-03-30 13:06:40 +00:00
parent 40e6069ff0
commit 2449dc2e80
3 changed files with 423 additions and 16 deletions

View File

@ -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();
}
}

View File

@ -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<TestResult> 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<String> expected, List<String> 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();
}
}

View File

@ -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());
}
}
}