Skip to content

Commit cc5c7ba

Browse files
committed
Fix enclosing class resolution with ClassFile API
Prior to this commit, the `ClassFile` based implementation of `AnnotationMetadata` would rely on the `NestHost` class element to get the enclosing class name for a nested class. This approach works for bytecode emitted by Java11+, which aligns with our Java17+ runtime policy. But there are cases where bytecode was not emitted by a Java11+ compiler, such as Kotlin. In this case, the `NestHost` class element is absent and we should instead use the `InnerClasses` information to get it. This commit makes use of `InnerClasses` to get the enclosing class name, but still uses `NestHost` as a fallback for anonymous classes. Fixes gh-36451
1 parent 0269eb8 commit cc5c7ba

2 files changed

Lines changed: 19 additions & 4 deletions

File tree

spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ static ClassFileAnnotationMetadata of(ClassModel classModel, @Nullable ClassLoad
195195
builder.accessFlags(flags);
196196
}
197197
case NestHostAttribute _ -> {
198-
builder.enclosingClass(classModel.thisClass());
198+
builder.enclosingClassFromNestHost(classModel.thisClass());
199199
}
200200
case InnerClassesAttribute innerClasses -> {
201201
builder.nestMembers(currentClassName, innerClasses);
@@ -256,11 +256,16 @@ void accessFlags(AccessFlags accessFlags) {
256256
this.accessFlags = accessFlags;
257257
}
258258

259-
void enclosingClass(ClassEntry thisClass) {
259+
void enclosingClassFromNestHost(ClassEntry thisClass) {
260+
if (this.enclosingClassName != null) {
261+
return;
262+
}
260263
String thisClassName = thisClass.name().stringValue();
261264
int currentClassIndex = thisClassName.lastIndexOf('$');
262-
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(
263-
thisClassName.substring(0, currentClassIndex));
265+
if (currentClassIndex > 0) {
266+
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(
267+
thisClassName.substring(0, currentClassIndex));
268+
}
264269
}
265270

266271
void superClass(Superclass superClass) {
@@ -280,6 +285,8 @@ void nestMembers(String currentClassName, InnerClassesAttribute innerClasses) {
280285
if (currentClassName.equals(innerClassName)) {
281286
// the current class is an inner class
282287
this.innerAccessFlags = classInfo.flags();
288+
classInfo.outerClass().ifPresent(outerClass ->
289+
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(outerClass.name().stringValue()));
283290
}
284291
classInfo.outerClass().ifPresent(outerClass -> {
285292
if (outerClass.name().stringValue().equals(currentClassName)) {

spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ void getEnclosingClassNameWhenHasNoEnclosingClassReturnsNull() {
148148
assertThat(get(AbstractAnnotationMetadataTests.class).getEnclosingClassName()).isNull();
149149
}
150150

151+
@Test
152+
void getEnclosingClassNameWhenNestedMemberClassReturnsImmediateEnclosingClass() {
153+
assertThat(get(TestNestedMemberClass.TestMemberClassInnerClassA.class).getEnclosingClassName())
154+
.isEqualTo(TestNestedMemberClass.class.getName());
155+
assertThat(get(TestNestedMemberClass.TestMemberClassInnerClassA.TestMemberClassInnerClassAA.class).getEnclosingClassName())
156+
.isEqualTo(TestNestedMemberClass.TestMemberClassInnerClassA.class.getName());
157+
}
158+
151159
@Test
152160
void getSuperClassNameWhenHasSuperClassReturnsName() {
153161
assertThat(get(TestSubclass.class).getSuperClassName()).isEqualTo(TestClass.class.getName());

0 commit comments

Comments
 (0)