more fixes 3

This commit is contained in:
2026-06-20 12:21:11 +02:00
parent c37aa92c78
commit 56cdf96f2d
4 changed files with 253 additions and 6 deletions

View File

@@ -123,17 +123,19 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
result.setMetadata(updatedMetadata); result.setMetadata(updatedMetadata);
} }
private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) { private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
// If the chain's target expression or qualifier indicates a specific machine name, verify it // First, check explicit variable targeting (like contextMachineId)
String targetVar = chain.getContextMachineId(); String targetVar = chain.getContextMachineId();
if (targetVar == null && chain.getTriggerPoint() != null) { if (targetVar == null && chain.getTriggerPoint() != null) {
targetVar = chain.getTriggerPoint().getStateMachineId(); targetVar = chain.getTriggerPoint().getStateMachineId();
} }
String simplifiedMachineName = currentMachineName != null ? currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase() : "";
if (targetVar != null && !targetVar.isEmpty()) { if (targetVar != null && !targetVar.isEmpty()) {
targetVar = targetVar.toLowerCase(); targetVar = targetVar.toLowerCase();
// E.g., if target is "myStateMachine", ensure current machine name contains "my" // E.g., if target is "myStateMachine", ensure current machine name contains "my"
String simplifiedMachineName = currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase();
// If the variable name ends with StateMachine, we extract the prefix // If the variable name ends with StateMachine, we extract the prefix
if (targetVar.endsWith("statemachine")) { if (targetVar.endsWith("statemachine")) {
String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length()); String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length());
@@ -142,6 +144,44 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
} }
} }
} }
// Second, filter by vertical routing based on Service class name
if (chain.getMethodChain() != null) {
boolean hasElectronicsService = false;
boolean hasComputerStoreService = false;
for (String method : chain.getMethodChain()) {
String className = method;
if (method.contains(".")) {
className = method.substring(0, method.lastIndexOf('.'));
if (className.contains(".")) {
className = className.substring(className.lastIndexOf('.') + 1);
}
}
String lowerClass = className.toLowerCase();
if (lowerClass.contains("electronics")) {
hasElectronicsService = true;
} else if (lowerClass.contains("computerstore")) {
hasComputerStoreService = true;
}
}
String lowerMachine = currentMachineName != null ? currentMachineName.toLowerCase() : "";
boolean isElectronicsMachine = lowerMachine.contains("electronics");
// If the machine is an electronics machine, it must NOT be triggered by computer store
if (isElectronicsMachine && hasComputerStoreService && !hasElectronicsService) {
return false;
}
// If the machine is NOT an electronics machine (e.g. standard computer store SM),
// it must NOT be triggered by electronics services
if (!isElectronicsMachine && hasElectronicsService && !hasComputerStoreService) {
return false;
}
}
return true; return true;
} }

View File

@@ -188,8 +188,12 @@ public class CallGraphBuilder {
if (declaredType != null) { if (declaredType != null) {
System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType); System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType);
List<String> typesToInspect = new ArrayList<>(); List<String> typesToInspect = new ArrayList<>();
typesToInspect.add(declaredType); if ("inline-instantiation".equals(sourceMethod)) {
typesToInspect.addAll(context.getImplementations(declaredType)); typesToInspect.add(declaredType);
} else {
typesToInspect.add(declaredType);
typesToInspect.addAll(context.getImplementations(declaredType));
}
System.out.println("DEEP TRACE IMPLS: " + typesToInspect); System.out.println("DEEP TRACE IMPLS: " + typesToInspect);
for (String type : typesToInspect) { for (String type : typesToInspect) {
@@ -340,7 +344,12 @@ public class CallGraphBuilder {
} else if (retExpr instanceof QualifiedName qn) { } else if (retExpr instanceof QualifiedName qn) {
constants.add(qn.toString()); constants.add(qn.toString());
} else if (retExpr instanceof SimpleName sn) { } else if (retExpr instanceof SimpleName sn) {
constants.add(sn.toString()); List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
if (!consts.isEmpty()) {
constants.addAll(consts);
} else {
constants.add(sn.toString());
}
} }
} }
} }
@@ -803,4 +812,43 @@ public class CallGraphBuilder {
} }
return expr; return expr;
} }
private List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
List<String> results = new ArrayList<>();
for (MethodDeclaration md : td.getMethods()) {
if (md.isConstructor() && md.getBody() != null) {
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(Assignment node) {
Expression left = node.getLeftHandSide();
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) ||
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) {
String val = constantResolver.resolve(node.getRightHandSide(), context);
if (val != null) results.add(val);
}
return super.visit(node);
}
@Override
public boolean visit(SuperConstructorInvocation node) {
for (Object argObj : node.arguments()) {
Expression arg = (Expression) argObj;
String val = constantResolver.resolve(arg, context);
if (val != null) {
if (val.startsWith("ENUM_SET:")) {
for (String eVal : val.substring(9).split(",")) {
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
}
} else {
results.add(val);
}
}
}
return super.visit(node);
}
});
}
}
return results;
}
} }

View File

@@ -218,4 +218,43 @@ class TransitionLinkerEnricherTest {
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty(); assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
} }
@Test
void shouldFilterCrossStateMachineRoutingByVertical() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("ASSEMBLED", "ASSEMBLED")));
t1.setEvent(Event.of("ASSEMBLE", "ASSEMBLE"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("ASSEMBLE").build())
.methodChain(List.of(
"com.example.ElectronicsOrderController.post()",
"com.example.ElectronicsOrderService.processOrderEvent()"
))
.build();
// Testing against Electronics SM - should match
AnalysisResult resultElectronics = AnalysisResult.builder()
.name("ElectronicsOwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(resultElectronics, null, null);
CallChain updatedChainElec = resultElectronics.getMetadata().getCallChains().get(0);
assertThat(updatedChainElec.getMatchedTransitions()).hasSize(1);
// Testing against ComputerStore SM - should NOT match because chain has Electronics service
AnalysisResult resultComputer = AnalysisResult.builder()
.name("OwnDeliveryStateMachineConfiguration") // the non-electronics one
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(resultComputer, null, null);
CallChain updatedChainComp = resultComputer.getMetadata().getCallChains().get(0);
assertThat(updatedChainComp.getMatchedTransitions()).isNullOrEmpty();
}
} }

View File

@@ -1520,4 +1520,124 @@ class CallGraphBuilderTest {
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_COMPLEX_ARGS"); org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_COMPLEX_ARGS");
} }
@Test
void shouldNotIncludeSubclassesForInlineInstantiation() throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void handleStatus() {
service.process(new BaseEvent().getEvent());
}
}
class BaseEvent {
public String getEvent() { return "BASE_EVENT_RETURN"; }
}
class SubEvent extends BaseEvent {
public String getEvent() { return "SUB_EVENT_RETURN"; }
}
class OrderService {
public void process(String event) {}
}
""";
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_subclass");
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("handleStatus").build();
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
// Only BASE_EVENT_RETURN should be found. SUB_EVENT_RETURN is a subclass and should not be included for direct instantiations
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("BASE_EVENT_RETURN");
}
@Test
void shouldResolveConstantsFromFieldAssignedInConstructor() throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void handleStatus() {
service.process(new OrderPickedUpEvent().getType());
}
}
class OrderPickedUpEvent {
private String type;
public OrderPickedUpEvent() {
this.type = "PICKED_UP_IN_CONSTRUCTOR";
}
public String getType() { return type; }
}
class OrderService {
public void process(String event) {}
}
""";
Path tempDir = Files.createTempDirectory("callgraph_field_const");
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("handleStatus").build();
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("PICKED_UP_IN_CONSTRUCTOR");
}
@Test
void shouldResolveConstantsFromFieldAssignedInSuperConstructor() throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void handleStatus() {
service.process(new OrderCancelledEvent().getType());
}
}
abstract class BaseEvent {
private String type;
public BaseEvent(String type, String otherVar) {
this.type = type;
}
public String getType() { return type; }
}
class OrderCancelledEvent extends BaseEvent {
public OrderCancelledEvent() {
super("CANCELLED_IN_SUPER", "someVar");
}
}
class OrderService {
public void process(String event) {}
}
""";
Path tempDir = Files.createTempDirectory("callgraph_super_const");
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("handleStatus").build();
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("CANCELLED_IN_SUPER");
}
} }