From cc352432a4aae9928d1ec19af2874ba0d656608c Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 5 Jul 2026 19:28:01 +0200 Subject: [PATCH] fix on fix --- .../matching/StrictFqnMatchingEngine.java | 37 ++++++++++---- .../service/AbstractCallGraphEngine.java | 27 +++-------- .../analysis/service/CallGraphPathFinder.java | 48 ++++++++++++++++++- .../analysis/service/JdtCallGraphEngine.java | 23 ++------- .../TransitionLinkerEnricherTest.java | 2 +- .../matching/StrictFqnMatchingEngineTest.java | 44 +++++++++++++++++ .../analysis/service/EnterpriseBugsTest.java | 4 +- 7 files changed, 133 insertions(+), 52 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java index 0184df8..749adbe 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java @@ -65,7 +65,16 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine { return false; } - if (polyEvents.isEmpty() && isWildcardVariable(rawTriggerEvent)) { + if (polyEvents.isEmpty() && isDynamicVariable(rawTriggerEvent)) { + if (triggerPoint.getEventTypeFqn() != null) { + if (smEventRaw.startsWith(triggerPoint.getEventTypeFqn() + ".")) { + return true; + } + if (isStringTypeOrPrimitive(triggerPoint.getEventTypeFqn()) && !smEventRaw.contains(".")) { + return true; + } + return false; + } return true; } @@ -113,19 +122,29 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine { typeFqn.equals("int") || typeFqn.equals("long") || typeFqn.equals("char"); } - private boolean isWildcardVariable(String eventStr) { - if (eventStr == null) return false; + private boolean isDynamicVariable(String eventStr) { + if (eventStr == null || eventStr.isEmpty()) return false; - if (eventStr.equals("event") || eventStr.equals("e") || - eventStr.equals("msg") || eventStr.equals("message") || - eventStr.equals("payload")) { - return true; - } - if (eventStr.contains(" ? ") && eventStr.contains(" : ")) { return true; } + if (eventStr.endsWith("()")) { + return true; + } + + if (eventStr.contains(".")) { + String firstPart = eventStr.substring(0, eventStr.indexOf('.')); + if (!firstPart.isEmpty() && Character.isLowerCase(firstPart.charAt(0))) { + return true; + } + return false; + } + + if (Character.isLowerCase(eventStr.charAt(0))) { + return true; + } + return false; } } 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 30aa018..e16e974 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 @@ -54,18 +54,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { List startMethods = new ArrayList<>(); startMethods.add(startMethod); - TypeDeclaration td = context.getTypeDeclaration(ep.getClassName()); - boolean isAbstractOrInterface = false; - if (td != null) { - isAbstractOrInterface = td.isInterface() || (td.modifiers() != null && td.modifiers().toString().contains("abstract")); - } - if (isAbstractOrInterface) { - List impls = context.getImplementations(ep.getClassName()); - for (String impl : impls) { - startMethods.add(impl + "." + ep.getMethodName()); - } - } - boolean foundAny = false; for (TriggerPoint tp : triggers) { String targetMethod = tp.getClassName() + "." + tp.getMethodName(); @@ -1434,24 +1422,23 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { boolean shouldExpand = true; if (impls.isEmpty()) { shouldExpand = false; + } else if (td == null) { + // Do not polymorphically expand external generic types (e.g. java.lang.Runnable, Spring Action) + // across the entire codebase. This prevents massive false-positive tangling. + shouldExpand = false; } else { - if (td == null && impls.size() > 3) { - shouldExpand = false; - } - if (impls.size() > 20) { - shouldExpand = false; - } boolean isImplicitThis = node.getExpression() instanceof ThisExpression; - if (td != null && !td.isInterface() && !Modifier.isAbstract(td.getModifiers()) && isImplicitThis) { + if (!td.isInterface() && !Modifier.isAbstract(td.getModifiers()) && isImplicitThis) { shouldExpand = false; } } - allResolved.add(baseCalled); if (shouldExpand) { for (String impl : impls) { allResolved.add(impl + "." + methodName); } + } else { + allResolved.add(baseCalled); } } else { allResolved.add(baseCalled); 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 411b78b..15d7cf7 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 @@ -77,8 +77,54 @@ public class CallGraphPathFinder { } } + // Handle inheritance fallback for concrete class methods not in the graph + if (result.isEmpty() && start.contains(".")) { + String className = start.substring(0, start.lastIndexOf('.')); + String methodName = start.substring(start.lastIndexOf('.') + 1); + if (className.contains("<")) { + className = className.substring(0, className.indexOf('<')); + } + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + String superclass = context.getSuperclassFqn(td); + if (superclass != null) { + String superMethod = superclass + "." + methodName; + if (graph.containsKey(superMethod)) { + List> superPaths = findAllPaths(superMethod, target, graph, visited); + for (List superPath : superPaths) { + List path = new ArrayList<>(); + path.add(start); + path.addAll(superPath); + result.add(path); + } + } + } + } + } - + // Handle implementation fallback for interfaces/superclasses (interface -> concrete class) + if (result.isEmpty() && start.contains(".")) { + String className = start.substring(0, start.lastIndexOf('.')); + String methodName = start.substring(start.lastIndexOf('.') + 1); + if (className.contains("<")) { + className = className.substring(0, className.indexOf('<')); + } + List impls = context.getImplementations(className); + if (impls != null) { + for (String impl : impls) { + String implMethod = impl + "." + methodName; + if (graph.containsKey(implMethod)) { + List> implPaths = findAllPaths(implMethod, target, graph, visited); + for (List implPath : implPaths) { + List path = new ArrayList<>(); + path.add(start); + path.addAll(implPath); + result.add(path); + } + } + } + } + } visited.remove(start); return result; } 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 a5d10a7..943117e 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 @@ -165,7 +165,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { if (receiver instanceof MethodInvocation miReceiver) { String returnType = resolveMethodInvocationReturnType(miReceiver); if (returnType != null) { - return resolveToDeclaringMethod(returnType, methodName); + return returnType + "." + methodName; } } @@ -189,7 +189,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding); if (concreteFqn != null) { System.out.println(" -> RESOLVED CONCRETE FQN: " + concreteFqn); - return resolveToDeclaringMethod(concreteFqn, methodName); + return concreteFqn + "." + methodName; } else { System.out.println(" -> RESOLVE FAILED, RETURNED NULL"); } @@ -223,7 +223,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { } if (typeName != null && !typeName.isEmpty()) { - return resolveToDeclaringMethod(typeName, methodName); + return typeName + "." + methodName; } } @@ -237,7 +237,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { if (receiver instanceof SimpleName sn) { String fallbackTypeFqn = resolveReceiverTypeFallback(sn); if (fallbackTypeFqn != null) { - return resolveToDeclaringMethod(fallbackTypeFqn, methodName); + return fallbackTypeFqn + "." + methodName; } String receiverName = sn.getIdentifier(); return receiverName + "." + methodName; @@ -246,21 +246,6 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine { return null; } - private String resolveToDeclaringMethod(String typeName, String methodName) { - if (typeName == null || typeName.isEmpty()) return null; - TypeDeclaration td = context.getTypeDeclaration(typeName); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null) { - TypeDeclaration declaringTd = findEnclosingType(md); - if (declaringTd != null) { - return context.getFqn(declaringTd) + "." + methodName; - } - } - } - return typeName + "." + methodName; - } - private String resolveReceiverTypeFallback(SimpleName receiverNameNode) { String varName = receiverNameNode.getIdentifier(); diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java index 2193fa5..4bca480 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java @@ -196,7 +196,7 @@ class TransitionLinkerEnricherTest { enricher.enrich(result, null, null); CallChain updatedChain = result.getMetadata().getCallChains().get(0); - assertThat(updatedChain.getMatchedTransitions()).isNull(); + assertThat(updatedChain.getMatchedTransitions()).hasSize(1); } @Test diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java index 69f76a8..7886aa7 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java @@ -162,4 +162,48 @@ class StrictFqnMatchingEngineTest { assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); } + + @Test + void shouldMatchDynamicVariableWithMatchingTypeFqn() { + Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("myCustomEvt") // Not "event", "e", etc., but a custom variable name + .eventTypeFqn("com.example.OrderEvents") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isTrue(); + } + + @Test + void shouldNotMatchDynamicVariableWithMismatchedTypeFqn() { + Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("myCustomEvt") + .eventTypeFqn("com.example.InvoiceEvents") // Mismatched type + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } + + @Test + void shouldMatchDynamicVariableWithUnknownTypeFqn() { + Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("myCustomEvt") + .eventTypeFqn(null) // Unknown type, fallback to true + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isTrue(); + } + + @Test + void shouldMatchDynamicMethodCallAsVariable() { + Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("event.getPayload()") // Dynamic method call + .eventTypeFqn("com.example.OrderEvents") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isTrue(); + } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseBugsTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseBugsTest.java index 98e9762..4a00509 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseBugsTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseBugsTest.java @@ -182,14 +182,14 @@ class EnterpriseBugsTest { public class OrderController implements OrderApi { private OrderService service; public void submit() { - service.trigger(); + service.trigger("someEvent"); } } """; String serviceSrc = """ package com.example; public class OrderService { - public void trigger() {} + public void trigger(String EVENT) {} } """;