Skip to content

Commit

Permalink
Fix StackOverflowException in AnnotationHierarchies
Browse files Browse the repository at this point in the history
  • Loading branch information
BoykoAlex committed Jun 13, 2024
1 parent 15f20b6 commit f14172d
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,7 @@ public static boolean isSubtypeOf(Annotation annotation, String fqAnnotationType

public static boolean hasTransitiveSuperAnnotationType(ITypeBinding typeBinding, String annotationType) {
synchronized(lock) {
if (typeBinding != null && annotationType != null) {
String qname = typeBinding.getQualifiedName();

// log.info("CHECK ANNOTATION TYPE: " + qname + " / " + annotationType);

if (annotationType.equals(qname)) {
return true;
}
else {
Collection<ITypeBinding> directSuperAnnotations = getDirectSuperAnnotations(typeBinding);
for (ITypeBinding superAnnotationBinding : directSuperAnnotations) {
if (hasTransitiveSuperAnnotationType(superAnnotationBinding, annotationType)) {
return true;
}
}
}
}

return false;
return isMetaAnnotation(typeBinding, annotationType::equals);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright (c) 2024 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.annotations;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;

public class AnnotationHierarchiesTests {

private List<Path> createdFiles = new ArrayList<>();

private Path createFile(String projectName, String packageName, String name, String content) throws Exception {
Path projectPath = Paths.get(getClass().getResource("/test-projects/" + projectName).toURI());
Path filePath = projectPath.resolve("src/main/java").resolve(packageName.replace('.', '/')).resolve(name);
Files.createDirectories(filePath.getParent());
createdFiles.add(Files.createFile(filePath));
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
return filePath;
}

private void clearTestFiles() {
for (Iterator<Path> itr = createdFiles.iterator(); itr.hasNext();) {
Path path = itr.next();
try {
Files.delete(path);
itr.remove();
} catch (IOException e) {
e.printStackTrace();
}
}
}

@AfterEach
void tearDown() {
clearTestFiles();
}

@Test
void circularAnnotations() throws Exception {
String projectName = "test-spring-validations";
IJavaProject project = ProjectsHarness.INSTANCE.mavenProject(projectName);
createFile(projectName, "test", "CustomComponent1.java", """
package test;
import org.springframework.stereotype.Component;
@Component
@CustomComponent2
public @interface CustomComponent1 {
}
""");
createFile(projectName, "test", "CustomComponent2.java", """
package test;
import org.springframework.stereotype.Component;
@Component
@CustomComponent1
public @interface CustomComponent2 {
}
""");
Path file = createFile(projectName, "test", "MyComponent.java", """
package test;
@CustomComponent1
public class MyComponent {
}
""");

SpringIndexerJava.createParser(project, true).createASTs(new String[] { file.toFile().toString() }, null, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
cu.accept(new ASTVisitor() {

@Override
public boolean visit(MarkerAnnotation node) {
ITypeBinding binding = node.resolveTypeBinding();
assertThat(binding).isNotNull();
assertThat(binding.getQualifiedName()).isEqualTo("test.CustomComponent1");

assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "test.CustomComponent2")).isTrue();
assertThat(AnnotationHierarchies.hasTransitiveSuperAnnotationType(binding, "org.springframework.context.annotation.Configuration")).isFalse();
assertThat(AnnotationHierarchies.isMetaAnnotation(binding, "test.CustomComponent2"::equals)).isTrue();
assertThat(AnnotationHierarchies.isMetaAnnotation(binding, "org.springframework.context.annotation.Configuration"::equals)).isFalse();
assertThat(AnnotationHierarchies.getDirectSuperAnnotations(binding).stream().toList().size()).isEqualTo(2);

IAnnotationBinding annotationBinding = node.resolveAnnotationBinding();
assertThat(annotationBinding).isNotNull();
assertThat(AnnotationHierarchies.getDirectSuperAnnotationBindings(annotationBinding).stream().toList().size()).isEqualTo(2);
assertThat(AnnotationHierarchies.isSubtypeOf(node, "test.CustomComponent2")).isTrue();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.stereotype.Component")).isTrue();
assertThat(AnnotationHierarchies.isSubtypeOf(node, "org.springframework.context.annotation.Configuration")).isFalse();
return super.visit(node);
}

});
}
}, null);

}
}

0 comments on commit f14172d

Please sign in to comment.