Skip to content

Commit 6490b74

Browse files
committed
Merge branch '3.5.x'
Closes gh-47310
2 parents 1cd14c9 + 9a1d9f6 commit 6490b74

File tree

2 files changed

+118
-3
lines changed

2 files changed

+118
-3
lines changed

loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/MainClassFinder.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public abstract class MainClassFinder {
6262

6363
private static final Type MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE, STRING_ARRAY_TYPE);
6464

65+
private static final Type PARAMETERLESS_MAIN_METHOD_TYPE = Type.getMethodType(Type.VOID_TYPE);
66+
6567
private static final String MAIN_METHOD_NAME = "main";
6668

6769
private static final FileFilter CLASS_FILE_FILTER = MainClassFinder::isClassFile;
@@ -292,10 +294,20 @@ private static class ClassDescriptor extends ClassVisitor {
292294

293295
private boolean mainMethodFound;
294296

297+
private boolean java25OrLater = false;
298+
295299
ClassDescriptor() {
296300
super(SpringAsmInfo.ASM_VERSION);
297301
}
298302

303+
@Override
304+
public void visit(int version, int access, String name, String signature, String superName,
305+
String[] interfaces) {
306+
if (version >= 69) {
307+
this.java25OrLater = true;
308+
}
309+
}
310+
299311
@Override
300312
public @Nullable AnnotationVisitor visitAnnotation(String desc, boolean visible) {
301313
this.annotationNames.add(Type.getType(desc).getClassName());
@@ -305,13 +317,24 @@ private static class ClassDescriptor extends ClassVisitor {
305317
@Override
306318
public @Nullable MethodVisitor visitMethod(int access, String name, String desc, String signature,
307319
String[] exceptions) {
308-
if (isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC) && MAIN_METHOD_NAME.equals(name)
309-
&& MAIN_METHOD_TYPE.getDescriptor().equals(desc)) {
310-
this.mainMethodFound = true;
320+
if (hasRequiredAccess(access) && MAIN_METHOD_NAME.equals(name)) {
321+
if (MAIN_METHOD_TYPE.getDescriptor().equals(desc)
322+
|| (this.java25OrLater && PARAMETERLESS_MAIN_METHOD_TYPE.getDescriptor().equals(desc))) {
323+
this.mainMethodFound = true;
324+
}
311325
}
312326
return null;
313327
}
314328

329+
private boolean hasRequiredAccess(int access) {
330+
if (this.java25OrLater) {
331+
return !isAccess(access, Opcodes.ACC_PRIVATE) && isAccess(access, Opcodes.ACC_STATIC);
332+
}
333+
else {
334+
return isAccess(access, Opcodes.ACC_PUBLIC, Opcodes.ACC_STATIC);
335+
}
336+
}
337+
315338
private boolean isAccess(int access, int... requiredOpsCodes) {
316339
for (int requiredOpsCode : requiredOpsCodes) {
317340
if ((access & requiredOpsCode) == 0) {

loader/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/MainClassFinderTests.java

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@
1616

1717
package org.springframework.boot.loader.tools;
1818

19+
import java.io.ByteArrayInputStream;
1920
import java.io.File;
21+
import java.lang.reflect.Modifier;
2022
import java.util.ArrayList;
2123
import java.util.List;
2224
import java.util.jar.JarFile;
2325

26+
import net.bytebuddy.ByteBuddy;
27+
import net.bytebuddy.ClassFileVersion;
28+
import net.bytebuddy.description.method.MethodDescription;
29+
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
30+
import net.bytebuddy.implementation.Implementation;
31+
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
32+
import net.bytebuddy.jar.asm.MethodVisitor;
33+
import net.bytebuddy.jar.asm.Opcodes;
2434
import org.junit.jupiter.api.BeforeEach;
2535
import org.junit.jupiter.api.Test;
2636
import org.junit.jupiter.api.io.TempDir;
@@ -179,6 +189,88 @@ void doWithJarMainMethods() throws Exception {
179189
}
180190
}
181191

192+
@Test
193+
void packagePrivateMainMethod() throws Exception {
194+
this.testJarFile.addFile("a/b/c/D.class", packagePrivateMainMethod(ClassFileVersion.JAVA_V25));
195+
ClassNameCollector callback = new ClassNameCollector();
196+
try (JarFile jarFile = this.testJarFile.getJarFile()) {
197+
MainClassFinder.doWithMainClasses(jarFile, null, callback);
198+
assertThat(callback.getClassNames()).hasToString("[a.b.c.D]");
199+
}
200+
}
201+
202+
@Test
203+
void packagePrivateMainMethodBeforeJava25() throws Exception {
204+
this.testJarFile.addFile("a/b/c/D.class", packagePrivateMainMethod(ClassFileVersion.JAVA_V24));
205+
ClassNameCollector callback = new ClassNameCollector();
206+
try (JarFile jarFile = this.testJarFile.getJarFile()) {
207+
MainClassFinder.doWithMainClasses(jarFile, null, callback);
208+
assertThat(callback.getClassNames()).isEmpty();
209+
}
210+
}
211+
212+
@Test
213+
void parameterlessMainMethod() throws Exception {
214+
this.testJarFile.addFile("a/b/c/D.class", parameterlessMainMethod(ClassFileVersion.JAVA_V25));
215+
ClassNameCollector callback = new ClassNameCollector();
216+
try (JarFile jarFile = this.testJarFile.getJarFile()) {
217+
MainClassFinder.doWithMainClasses(jarFile, null, callback);
218+
assertThat(callback.getClassNames()).hasToString("[a.b.c.D]");
219+
}
220+
}
221+
222+
@Test
223+
void parameterlessMainMethodBeforeJava25() throws Exception {
224+
this.testJarFile.addFile("a/b/c/D.class", parameterlessMainMethod(ClassFileVersion.JAVA_V24));
225+
ClassNameCollector callback = new ClassNameCollector();
226+
try (JarFile jarFile = this.testJarFile.getJarFile()) {
227+
MainClassFinder.doWithMainClasses(jarFile, null, callback);
228+
assertThat(callback.getClassNames()).isEmpty();
229+
}
230+
}
231+
232+
private ByteArrayInputStream packagePrivateMainMethod(ClassFileVersion classFileVersion) {
233+
byte[] bytecode = new ByteBuddy(classFileVersion).subclass(Object.class)
234+
.defineMethod("main", void.class, Modifier.STATIC)
235+
.withParameter(String[].class)
236+
.intercept(new EmptyBodyImplementation())
237+
.make()
238+
.getBytes();
239+
return new ByteArrayInputStream(bytecode);
240+
}
241+
242+
private ByteArrayInputStream parameterlessMainMethod(ClassFileVersion classFileVersion) {
243+
byte[] bytecode = new ByteBuddy(classFileVersion).subclass(Object.class)
244+
.defineMethod("main", void.class, Modifier.STATIC | Modifier.PUBLIC)
245+
.intercept(new EmptyBodyImplementation())
246+
.make()
247+
.getBytes();
248+
return new ByteArrayInputStream(bytecode);
249+
}
250+
251+
static class EmptyBodyImplementation implements Implementation {
252+
253+
@Override
254+
public InstrumentedType prepare(InstrumentedType instrumentedType) {
255+
return instrumentedType;
256+
}
257+
258+
@Override
259+
public ByteCodeAppender appender(Target implementationTarget) {
260+
return new ByteCodeAppender() {
261+
262+
@Override
263+
public Size apply(MethodVisitor methodVisitor, Context implementationContext,
264+
MethodDescription instrumentedMethod) {
265+
methodVisitor.visitInsn(Opcodes.RETURN);
266+
return Size.ZERO;
267+
}
268+
269+
};
270+
}
271+
272+
}
273+
182274
static class ClassNameCollector implements MainClassCallback<Object> {
183275

184276
private final List<String> classNames = new ArrayList<>();

0 commit comments

Comments
 (0)