event maching heuristic update 2

This commit is contained in:
2026-06-21 14:06:08 +02:00
parent 968601eefc
commit fc267c43c6
5 changed files with 417 additions and 14 deletions

View File

@@ -12,16 +12,24 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
return false; return false;
} }
String triggerEvent = simplify(triggerPoint.getEvent()); String rawTriggerEvent = triggerPoint.getEvent();
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName(); String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
String smEvent = simplify(smEventRaw); String smEvent = simplify(smEventRaw);
boolean isWildcard = isWildcardVariable(triggerEvent); boolean isWildcard = isWildcardVariable(rawTriggerEvent);
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList(); List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
boolean hasPolyMatch = false; boolean hasPolyMatch = false;
for (String pe : polyEvents) { for (String pe : polyEvents) {
if (pe.contains(".") && smEventRaw.contains(".")) {
if (smEventRaw.equals(pe) || smEventRaw.endsWith("." + pe)) {
hasPolyMatch = true;
break;
}
continue; // Stricter matching: do not fallback if both have qualifiers but don't match
}
String simplePe = pe; String simplePe = pe;
if (pe.contains(".")) { if (pe.contains(".")) {
simplePe = pe.substring(pe.lastIndexOf('.') + 1); simplePe = pe.substring(pe.lastIndexOf('.') + 1);
@@ -35,7 +43,18 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
} }
} }
return hasPolyMatch || smEvent.equals(triggerEvent) || (polyEvents.isEmpty() && isWildcard); if (hasPolyMatch) return true;
if (polyEvents.isEmpty() && isWildcard) return true;
if (rawTriggerEvent.contains(".") && smEventRaw.contains(".")) {
if (smEventRaw.equals(rawTriggerEvent) || smEventRaw.endsWith("." + rawTriggerEvent)) {
return true;
}
return false;
}
String triggerEvent = simplify(rawTriggerEvent);
return smEvent.equals(triggerEvent);
} }
private boolean isWildcardVariable(String eventStr) { private boolean isWildcardVariable(String eventStr) {

View File

@@ -129,6 +129,29 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
String entryMethod = path.get(0); String entryMethod = path.get(0);
int entryParamIndex = getParameterIndex(entryMethod, currentParamName); int entryParamIndex = getParameterIndex(entryMethod, currentParamName);
if (entryParamIndex < 0) { if (entryParamIndex < 0) {
// Intercept local setter before falling back to initializer
if (methodSuffix.startsWith(".get") || methodSuffix.equals(".type") || methodSuffix.equals(".event") || methodSuffix.equals(".type()") || methodSuffix.equals(".event()")) {
String localMethodName = methodSuffix.substring(1).replace("()", "");
org.eclipse.jdt.core.dom.Expression localSetterExpr = traceLocalSetter(entryMethod, currentParamName, localMethodName);
if (localSetterExpr != null) {
List<String> setterEvents = new ArrayList<>();
extractConstantsFromExpression(localSetterExpr, setterEvents);
if (!setterEvents.isEmpty()) {
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(setterEvents)
.build();
}
}
}
String tracedVar = traceLocalVariable(entryMethod, currentParamName); String tracedVar = traceLocalVariable(entryMethod, currentParamName);
if (tracedVar != null && !tracedVar.equals(currentParamName)) { if (tracedVar != null && !tracedVar.equals(currentParamName)) {
int dotIdx = tracedVar.indexOf('.'); int dotIdx = tracedVar.indexOf('.');
@@ -148,6 +171,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
exprParser.setSource(resolvedValue.toCharArray()); exprParser.setSource(resolvedValue.toCharArray());
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION); exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null); org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
System.out.println("resolvedValue = " + resolvedValue + " exprNode=" + exprNode.getClass().getSimpleName());
String varName = null; String varName = null;
String methodName = null; String methodName = null;
@@ -204,6 +228,29 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
} }
if (methodName != null) { if (methodName != null) {
System.out.println("Checking local setter for: varName=" + varName + " methodName=" + methodName + " entryMethod=" + entryMethod);
if (varName != null && !varName.isEmpty() && Character.isLowerCase(varName.charAt(0)) && !methodName.equals("VariableReference")) {
org.eclipse.jdt.core.dom.Expression localSetterExpr = traceLocalSetter(entryMethod, varName, methodName);
System.out.println("localSetterExpr = " + (localSetterExpr != null ? localSetterExpr.toString() : "null"));
if (localSetterExpr != null) {
extractConstantsFromExpression(localSetterExpr, polymorphicEvents);
System.out.println("Extracted from setter: " + polymorphicEvents);
if (!polymorphicEvents.isEmpty()) {
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)
.build();
}
}
}
if (varName != null && declaredType == null) { if (varName != null && declaredType == null) {
for (String methodFqn : path) { for (String methodFqn : path) {
declaredType = getVariableDeclaredType(methodFqn, varName); declaredType = getVariableDeclaredType(methodFqn, varName);
@@ -423,22 +470,18 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
} }
} }
if (!handled) { if (!handled) {
extractConstantsFromExpression(retExpr, constants);
String val = constantResolver.resolve(retExpr, context); String val = constantResolver.resolve(retExpr, context);
if (val != null) { if (val != null) {
if (val.startsWith("ENUM_SET:")) { if (val.startsWith("ENUM_SET:")) {
for (String eVal : val.substring(9).split(",")) { for (String eVal : val.substring(9).split(",")) {
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1)); String parsed = eVal.substring(eVal.lastIndexOf('.') + 1);
if (!constants.contains(parsed)) constants.add(parsed);
} }
} else { } else {
constants.add(val); if (!constants.contains(val)) constants.add(val);
} }
} else if (retExpr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) { } else if (retExpr instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
extractConstantsFromExpression(ce, constants);
} else if (retExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
constants.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()));
} else if (retExpr instanceof QualifiedName qn) {
constants.add(qn.toString());
} else if (retExpr instanceof SimpleName sn) {
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited); List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
if (!consts.isEmpty()) { if (!consts.isEmpty()) {
constants.addAll(consts); constants.addAll(consts);
@@ -491,9 +534,87 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
extractConstantsFromExpression(pe.getExpression(), constants); extractConstantsFromExpression(pe.getExpression(), constants);
} else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) { } else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) {
extractConstantsFromExpression(ce.getExpression(), constants); extractConstantsFromExpression(ce.getExpression(), constants);
} else if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
se.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
@Override
public boolean visit(org.eclipse.jdt.core.dom.YieldStatement ys) {
extractConstantsFromExpression(ys.getExpression(), constants);
return super.visit(ys);
}
@Override
public boolean visit(org.eclipse.jdt.core.dom.ExpressionStatement es) {
extractConstantsFromExpression(es.getExpression(), constants);
return super.visit(es);
}
@Override
public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) {
extractConstantsFromExpression(rs.getExpression(), constants);
return super.visit(rs);
}
});
} else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
String methodName = mi.getName().getIdentifier();
// 1. Local setter tracking
if ((methodName.startsWith("get") || methodName.equals("type") || methodName.equals("event")) && mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
String varName = sn.getIdentifier();
String propName = methodName.startsWith("get") ? methodName.substring(3) : methodName;
org.eclipse.jdt.core.dom.Block block = findEnclosingBlock(mi);
if (block != null) {
for (Object stmtObj : block.statements()) {
if (stmtObj == mi.getParent() || stmtObj == mi) break;
if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es) {
if (es.getExpression() instanceof org.eclipse.jdt.core.dom.MethodInvocation setterMi) {
if (setterMi.getName().getIdentifier().equalsIgnoreCase("set" + propName) || setterMi.getName().getIdentifier().equalsIgnoreCase(propName)) {
if (setterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName setterSn && setterSn.getIdentifier().equals(varName)) {
if (!setterMi.arguments().isEmpty()) {
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) setterMi.arguments().get(0), constants);
return;
}
}
}
} else if (es.getExpression() instanceof org.eclipse.jdt.core.dom.Assignment assignment) {
if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) {
if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) {
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
return;
}
}
} else if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) {
if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) {
if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) {
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
return;
}
}
}
}
}
}
}
}
// 2. Delegate to method return analysis
TypeDeclaration td = findEnclosingType(mi);
System.out.println("DELEGATING MethodInvocation: " + methodName + " td=" + (td != null ? td.getName() : "null") + " fqn=" + (td != null ? context.getFqn(td) : "null"));
if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
List<String> values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null);
System.out.println("RESOLVED MethodInvocation values: " + values);
if (values != null) constants.addAll(values);
}
} }
} }
private org.eclipse.jdt.core.dom.Block findEnclosingBlock(org.eclipse.jdt.core.dom.ASTNode node) {
org.eclipse.jdt.core.dom.ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof org.eclipse.jdt.core.dom.Block)) {
parent = parent.getParent();
}
return (org.eclipse.jdt.core.dom.Block) parent;
}
private String traceLocalVariable(String methodFqn, String varName) { private String traceLocalVariable(String methodFqn, String varName) {
if (methodFqn == null || !methodFqn.contains(".")) return null; if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
@@ -546,6 +667,52 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
return null; return null;
} }
private org.eclipse.jdt.core.dom.Expression traceLocalSetter(String methodFqn, String varName, String getterName) {
if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
org.eclipse.jdt.core.dom.MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null && md.getBody() != null) {
final org.eclipse.jdt.core.dom.Expression[] setterArg = new org.eclipse.jdt.core.dom.Expression[1];
String propName = getterName.startsWith("get") ? getterName.substring(3) : getterName;
md.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
@Override
public boolean visit(org.eclipse.jdt.core.dom.MethodInvocation node) {
if (node.getName().getIdentifier().equalsIgnoreCase("set" + propName) || node.getName().getIdentifier().equalsIgnoreCase(propName)) {
if (node.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn && sn.getIdentifier().equals(varName)) {
if (!node.arguments().isEmpty()) {
setterArg[0] = (org.eclipse.jdt.core.dom.Expression) node.arguments().get(0);
}
}
}
return super.visit(node);
}
@Override
public boolean visit(org.eclipse.jdt.core.dom.Assignment node) {
if (node.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) {
if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) {
setterArg[0] = node.getRightHandSide();
}
}
} else if (node.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) {
if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) {
if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) {
setterArg[0] = node.getRightHandSide();
}
}
}
return super.visit(node);
}
});
return setterArg[0];
}
}
return null;
}
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) { private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
if (depth > 5) return mi; if (depth > 5) return mi;
if (!mi.arguments().isEmpty()) { if (!mi.arguments().isEmpty()) {

View File

@@ -427,14 +427,16 @@ public class JdtCallGraphEngine implements CallGraphEngine {
} }
} }
if (!handled) { if (!handled) {
extractConstantsFromExpression(retExpr, constants);
String val = constantResolver.resolve(retExpr, context); String val = constantResolver.resolve(retExpr, context);
if (val != null) { if (val != null) {
if (val.startsWith("ENUM_SET:")) { if (val.startsWith("ENUM_SET:")) {
for (String eVal : val.substring(9).split(",")) { for (String eVal : val.substring(9).split(",")) {
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1)); String parsed = eVal.substring(eVal.lastIndexOf('.') + 1);
if (!constants.contains(parsed)) constants.add(parsed);
} }
} else { } else {
constants.add(val); if (!constants.contains(val)) constants.add(val);
} }
} else if (retExpr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) { } else if (retExpr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) {
extractConstantsFromExpression(ce, constants); extractConstantsFromExpression(ce, constants);
@@ -494,9 +496,85 @@ public class JdtCallGraphEngine implements CallGraphEngine {
extractConstantsFromExpression(pe.getExpression(), constants); extractConstantsFromExpression(pe.getExpression(), constants);
} else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) { } else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) {
extractConstantsFromExpression(ce.getExpression(), constants); extractConstantsFromExpression(ce.getExpression(), constants);
} else if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
se.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
@Override
public boolean visit(org.eclipse.jdt.core.dom.YieldStatement ys) {
extractConstantsFromExpression(ys.getExpression(), constants);
return super.visit(ys);
}
@Override
public boolean visit(org.eclipse.jdt.core.dom.ExpressionStatement es) {
extractConstantsFromExpression(es.getExpression(), constants);
return super.visit(es);
}
@Override
public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) {
extractConstantsFromExpression(rs.getExpression(), constants);
return super.visit(rs);
}
});
} else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
String methodName = mi.getName().getIdentifier();
// 1. Local setter tracking
if ((methodName.startsWith("get") || methodName.equals("type") || methodName.equals("event")) && mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
String varName = sn.getIdentifier();
String propName = methodName.startsWith("get") ? methodName.substring(3) : methodName;
org.eclipse.jdt.core.dom.Block block = findEnclosingBlock(mi);
if (block != null) {
for (Object stmtObj : block.statements()) {
if (stmtObj == mi.getParent() || stmtObj == mi) break;
if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es) {
if (es.getExpression() instanceof org.eclipse.jdt.core.dom.MethodInvocation setterMi) {
if (setterMi.getName().getIdentifier().equalsIgnoreCase("set" + propName) || setterMi.getName().getIdentifier().equalsIgnoreCase(propName)) {
if (setterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName setterSn && setterSn.getIdentifier().equals(varName)) {
if (!setterMi.arguments().isEmpty()) {
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) setterMi.arguments().get(0), constants);
return;
}
}
}
} else if (es.getExpression() instanceof org.eclipse.jdt.core.dom.Assignment assignment) {
if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) {
if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) {
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
return;
}
}
} else if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) {
if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) {
if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) {
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
return;
}
}
}
}
}
}
}
}
// 2. Delegate to method return analysis
TypeDeclaration td = findEnclosingType(mi);
if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
List<String> values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null);
if (values != null) constants.addAll(values);
}
} }
} }
private org.eclipse.jdt.core.dom.Block findEnclosingBlock(org.eclipse.jdt.core.dom.ASTNode node) {
org.eclipse.jdt.core.dom.ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof org.eclipse.jdt.core.dom.Block)) {
parent = parent.getParent();
}
return (org.eclipse.jdt.core.dom.Block) parent;
}
private String traceLocalVariable(String methodFqn, String varName) { private String traceLocalVariable(String methodFqn, String varName) {
if (methodFqn == null || !methodFqn.contains(".")) return null; if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));

View File

@@ -0,0 +1,78 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class HeuristicEventMatchingEngineTest {
private final HeuristicEventMatchingEngine engine = new HeuristicEventMatchingEngine();
@Test
void shouldMatchExactFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("com.example.OrderEvents.PAY").build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchEnumQualifierToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("OrderEvents.PAY").build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldPreventCrossEnumContamination() {
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("OrderEvents.PAY").build();
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
}
@Test
void shouldPreventCrossEnumContaminationWithPolymorphicEvents() {
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("OrderEvents.PAY")).build();
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
}
@Test
void shouldMatchPolymorphicEnumQualifierToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("OrderEvents.PAY")).build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchRawStringToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchRawStringToRawString() {
Event smEvent = Event.of("PAY", "PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
@Test
void shouldMatchPolymorphicRawStringToFqn() {
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
TriggerPoint triggerPoint = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("PAY")).build();
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
}
}

View File

@@ -18,6 +18,67 @@ import static org.assertj.core.api.Assertions.assertThat;
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis") @Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
class HeuristicCallGraphEngineTest { class HeuristicCallGraphEngineTest {
@Test
void shouldResolveLocalSetterAndSwitchExpressionPolymorphism(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderService {
public void updateOrderState(String vv) {
StateMachine sm = new StateMachine();
MysteryPayload a = new MysteryPayload();
a.setType(my_func(vv));
sm.sendEvent(a.getType());
}
private OrderEvents my_func(String q) {
return switch (q) {
case "a" -> OrderEvents.A8;
case "b" -> OrderEvents.A133;
default -> throw new IllegalArgumentException("Unknown");
};
}
}
enum OrderEvents { A8, A133 }
class MysteryPayload {
private OrderEvents type;
public void setType(OrderEvents type) { this.type = type; }
public OrderEvents getType() { return type; }
}
class StateMachine {
public void sendEvent(OrderEvents event) {}
}
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderService")
.methodName("updateOrderState")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.StateMachine")
.methodName("sendEvent")
.event("event")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
CallChain chain = chains.get(0);
System.out.println("Resolved poly events: " + chain.getTriggerPoint().getPolymorphicEvents());
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133");
}
@Test @Test
void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException { void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException {
String source = """ String source = """