better enricher attempt

This commit is contained in:
2026-06-19 21:21:44 +02:00
parent 7077214c81
commit bf9208d529
4 changed files with 115 additions and 7 deletions

View File

@@ -51,10 +51,10 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
if (targetVar == null && chain.getTriggerPoint() != null) { if (targetVar == null && chain.getTriggerPoint() != null) {
targetVar = chain.getTriggerPoint().getStateMachineId(); targetVar = chain.getTriggerPoint().getStateMachineId();
} }
if (targetVar == null || targetVar.isEmpty()) { // We no longer hard-block wildcards without a specific routing context.
// Wildcard without specific routing context is too broad, do not link // If a project doesn't use standard SM persisters (e.g. restores state manually),
continue; // contextMachineId will be null. We should still link the wildcard to provide SOME visibility,
} // rather than completely hiding the endpoint.
} }
if (isWildcard || smEvent.equals(triggerEvent) || triggerEvent.toLowerCase().contains(smEvent.toLowerCase()) || smEvent.toLowerCase().contains(triggerEvent.toLowerCase())) { if (isWildcard || smEvent.equals(triggerEvent) || triggerEvent.toLowerCase().contains(smEvent.toLowerCase()) || smEvent.toLowerCase().contains(triggerEvent.toLowerCase())) {

View File

@@ -84,6 +84,15 @@ public class CallGraphBuilder {
// Find parameter index in target method // Find parameter index in target method
int paramIndex = getParameterIndex(target, currentParamName); int paramIndex = getParameterIndex(target, currentParamName);
if (paramIndex < 0) {
// Not a parameter. Maybe it's a local variable initialized from a parameter or method call?
String tracedVar = traceLocalVariable(target, currentParamName);
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
currentParamName = tracedVar;
// We must try to find paramIndex again, because the traced variable might be a parameter of this same method
paramIndex = getParameterIndex(target, currentParamName);
}
}
if (paramIndex < 0) { if (paramIndex < 0) {
break; // Parameter name changed or not found, stop tracing break; // Parameter name changed or not found, stop tracing
} }
@@ -141,6 +150,51 @@ public class CallGraphBuilder {
return -1; return -1;
} }
private String traceLocalVariable(String methodFqn, String varName) {
if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null && md.getBody() != null) {
final Expression[] initializer = new Expression[1];
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializer[0] = node.getInitializer();
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializer[0] = node.getRightHandSide();
}
return super.visit(node);
}
});
if (initializer[0] != null) {
Expression expr = traceVariable(initializer[0]);
if (expr instanceof MethodInvocation mi) {
// e.g. event.getType() -> return "event.getType()" or just "event"
if (mi.getExpression() instanceof SimpleName sn) {
return sn.getIdentifier() + "." + mi.getName().getIdentifier() + "()";
}
return mi.getName().getIdentifier() + "()";
}
if (expr instanceof SimpleName sn) {
return sn.getIdentifier();
}
return expr.toString();
}
}
}
return null;
}
private String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) { private String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
for (String node : path) { for (String node : path) {
List<CallEdge> edges = callGraph.get(node); List<CallEdge> edges = callGraph.get(node);

View File

@@ -168,7 +168,7 @@ class TransitionLinkerEnricherTest {
} }
@Test @Test
void shouldIgnoreWildcardWhenContextMachineIdIsMissing() { void shouldAllowWildcardWhenContextMachineIdIsMissing() {
Transition t1 = new Transition(); Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW"))); t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID"))); t1.setTargetStates(List.of(State.of("PAID", "PAID")));
@@ -176,7 +176,7 @@ class TransitionLinkerEnricherTest {
CallChain chain = CallChain.builder() CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("event").build()) .triggerPoint(TriggerPoint.builder().event("event").build())
// Intentionally NO contextMachineId or stateMachineId // NO contextMachineId or stateMachineId
.build(); .build();
AnalysisResult result = AnalysisResult.builder() AnalysisResult result = AnalysisResult.builder()
@@ -188,6 +188,6 @@ class TransitionLinkerEnricherTest {
enricher.enrich(result, null, null); enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0); CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty(); assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
} }
} }

View File

@@ -165,4 +165,58 @@ class CallGraphBuilderTest {
CallChain chain = chains.get(0); CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.CREATE"); assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.CREATE");
} }
@Test
void shouldResolveTriggerPointLocalVariableAcrossChain() throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void processOrderEvent(MyOrderEvent domainEvent) {
doProcessOrderEvent(domainEvent);
}
private void doProcessOrderEvent(MyOrderEvent domainEvent) {
OrderEvents eventType = domainEvent.getType();
service.updateOrderState(eventType);
}
}
class OrderService {
public void updateOrderState(OrderEvents event) {
// sendEvent
}
}
class MyOrderEvent {
public OrderEvents getType() { return OrderEvents.PAY; }
}
enum OrderEvents { CREATE, PAY }
""";
Path tempDir = Files.createTempDirectory("callgraph_test2");
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
CallGraphBuilder builder = new CallGraphBuilder(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("processOrderEvent")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.OrderService")
.methodName("updateOrderState")
.event("event")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("domainEvent.getType()");
}
} }