From 23af434504cc7ab839abec6bfc4c09f22220a8e5 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 5 Jul 2026 20:02:02 +0200 Subject: [PATCH] update --- .../service/AbstractCallGraphEngine.java | 30 +++++++ .../analysis/service/CallGraphPathFinder.java | 1 + .../analysis/service/JdtCallGraphEngine.java | 5 +- .../service/CallGraphPathFinderTest.java | 81 +++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinderTest.java diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java index e16e974..200cb92 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -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 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 polyEventsRef = polymorphicEvents; for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java index 15d7cf7..bb54082 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java @@ -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 path = new ArrayList<>(List.of(start, target)); result.add(path); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java index 943117e..2a24370 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java @@ -223,7 +223,10 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { } if (typeName != null && !typeName.isEmpty()) { - return typeName + "." + methodName; + org.eclipse.jdt.core.dom.TypeDeclaration resolvedTd = context.getTypeDeclaration(typeName); + if (resolvedTd != null && context.findMethodDeclaration(resolvedTd, methodName, true) != null) { + return typeName + "." + methodName; + } } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinderTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinderTest.java new file mode 100644 index 0000000..c631ffa --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinderTest.java @@ -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> 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> 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> 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> 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" + ); + } +}