This commit is contained in:
2026-07-05 20:02:02 +02:00
parent cc352432a4
commit 23af434504
4 changed files with 116 additions and 1 deletions

View File

@@ -357,6 +357,36 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
} else if (mi.getExpression() instanceof ClassInstanceCreation cic) {
declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
sourceMethod = "inline-instantiation";
List<String> getterResults = constructorAnalyzer.resolveInlineInstantiationGetterResult(cic, methodName, context, new java.util.HashSet<>());
if (getterResults != null && !getterResults.isEmpty()) {
for (String gr : getterResults) {
if (gr.startsWith("ENUM_SET:")) {
for (String eVal : gr.substring(9).split(",")) {
String parsed = constantExtractor.parseEnumSetElement(eVal);
if (!polymorphicEvents.contains(parsed)) polymorphicEvents.add(parsed);
}
} else {
if (!polymorphicEvents.contains(gr)) polymorphicEvents.add(gr);
}
}
return TriggerPoint.builder()
.event(tp.getEvent())
.className(tp.getClassName())
.methodName(tp.getMethodName())
.sourceFile(tp.getSourceFile())
.sourceModule(tp.getSourceModule())
.stateMachineId(tp.getStateMachineId())
.sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.stateTypeFqn(tp.getStateTypeFqn())
.eventTypeFqn(tp.getEventTypeFqn())
.external(tp.isExternal())
.build();
}
if (cic.getAnonymousClassDeclaration() != null) {
final List<String> polyEventsRef = polymorphicEvents;
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {

View File

@@ -62,6 +62,7 @@ public class CallGraphPathFinder {
if (neighbors != null) {
for (CallEdge edge : neighbors) {
String neighbor = edge.getTargetMethod();
if (neighbor.startsWith("java.") || neighbor.startsWith("javax.") || neighbor.startsWith("jakarta.") || neighbor.startsWith("org.springframework.")) continue;
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
List<String> path = new ArrayList<>(List.of(start, target));
result.add(path);

View File

@@ -223,9 +223,12 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
}
if (typeName != null && !typeName.isEmpty()) {
org.eclipse.jdt.core.dom.TypeDeclaration resolvedTd = context.getTypeDeclaration(typeName);
if (resolvedTd != null && context.findMethodDeclaration(resolvedTd, methodName, true) != null) {
return typeName + "." + methodName;
}
}
}
if (receiver instanceof ThisExpression) {
TypeDeclaration td = findEnclosingType(node);

View File

@@ -0,0 +1,81 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
class CallGraphPathFinderTest {
private CallGraphPathFinder pathFinder;
@BeforeEach
void setUp() {
pathFinder = new CallGraphPathFinder(new CodebaseContext());
}
@Test
void shouldSkipFrameworkAndJdkPaths() {
Map<String, List<CallEdge>> graph = new HashMap<>();
// Setup an anonymized call graph where business logic delegates to a platform boundary
// e.g. BusinessService.process -> java.util.stream.Stream.map -> AnotherBusinessService.doWork
// The pathfinder should skip the java.* path, avoiding spurious hops.
graph.put("com.acme.BusinessService.process", List.of(
new CallEdge("java.util.stream.Stream.map", List.of()),
new CallEdge("com.acme.DirectService.execute", List.of())
));
graph.put("java.util.stream.Stream.map", List.of(
new CallEdge("com.acme.AnotherBusinessService.doWork", List.of())
));
graph.put("com.acme.DirectService.execute", List.of(
new CallEdge("com.acme.AnotherBusinessService.doWork", List.of())
));
List<List<String>> paths = pathFinder.findAllPaths(
"com.acme.BusinessService.process",
"com.acme.AnotherBusinessService.doWork",
graph,
new HashSet<>()
);
// It should ONLY find the direct business path, skipping the java.util path.
assertThat(paths).hasSize(1);
assertThat(paths.get(0)).containsExactly(
"com.acme.BusinessService.process",
"com.acme.DirectService.execute",
"com.acme.AnotherBusinessService.doWork"
);
}
@Test
void shouldFollowValidBusinessPaths() {
Map<String, List<CallEdge>> graph = new HashMap<>();
graph.put("com.acme.Start.run", List.of(
new CallEdge("com.acme.Middle.process", List.of())
));
graph.put("com.acme.Middle.process", List.of(
new CallEdge("com.acme.End.finish", List.of())
));
List<List<String>> paths = pathFinder.findAllPaths(
"com.acme.Start.run",
"com.acme.End.finish",
graph,
new HashSet<>()
);
assertThat(paths).hasSize(1);
assertThat(paths.get(0)).containsExactly(
"com.acme.Start.run",
"com.acme.Middle.process",
"com.acme.End.finish"
);
}
}