Browse Source

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
pull/36469/head
Brian Clozel 6 days ago
parent
commit
cc5c7ba186
  1. 15
      spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java
  2. 8
      spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java

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

@ -195,7 +195,7 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata { @@ -195,7 +195,7 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata {
builder.accessFlags(flags);
}
case NestHostAttribute _ -> {
builder.enclosingClass(classModel.thisClass());
builder.enclosingClassFromNestHost(classModel.thisClass());
}
case InnerClassesAttribute innerClasses -> {
builder.nestMembers(currentClassName, innerClasses);
@ -256,11 +256,16 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata { @@ -256,11 +256,16 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata {
this.accessFlags = accessFlags;
}
void enclosingClass(ClassEntry thisClass) {
void enclosingClassFromNestHost(ClassEntry thisClass) {
if (this.enclosingClassName != null) {
return;
}
String thisClassName = thisClass.name().stringValue();
int currentClassIndex = thisClassName.lastIndexOf('$');
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(
thisClassName.substring(0, currentClassIndex));
if (currentClassIndex > 0) {
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(
thisClassName.substring(0, currentClassIndex));
}
}
void superClass(Superclass superClass) {
@ -280,6 +285,8 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata { @@ -280,6 +285,8 @@ final class ClassFileAnnotationMetadata implements AnnotationMetadata {
if (currentClassName.equals(innerClassName)) {
// the current class is an inner class
this.innerAccessFlags = classInfo.flags();
classInfo.outerClass().ifPresent(outerClass ->
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(outerClass.name().stringValue()));
}
classInfo.outerClass().ifPresent(outerClass -> {
if (outerClass.name().stringValue().equals(currentClassName)) {

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

@ -148,6 +148,14 @@ public abstract class AbstractAnnotationMetadataTests { @@ -148,6 +148,14 @@ public abstract class AbstractAnnotationMetadataTests {
assertThat(get(AbstractAnnotationMetadataTests.class).getEnclosingClassName()).isNull();
}
@Test
void getEnclosingClassNameWhenNestedMemberClassReturnsImmediateEnclosingClass() {
assertThat(get(TestNestedMemberClass.TestMemberClassInnerClassA.class).getEnclosingClassName())
.isEqualTo(TestNestedMemberClass.class.getName());
assertThat(get(TestNestedMemberClass.TestMemberClassInnerClassA.TestMemberClassInnerClassAA.class).getEnclosingClassName())
.isEqualTo(TestNestedMemberClass.TestMemberClassInnerClassA.class.getName());
}
@Test
void getSuperClassNameWhenHasSuperClassReturnsName() {
assertThat(get(TestSubclass.class).getSuperClassName()).isEqualTo(TestClass.class.getName());

Loading…
Cancel
Save