fix on fix

This commit is contained in:
2026-07-05 19:28:01 +02:00
parent 78c0f3e705
commit cc352432a4
7 changed files with 133 additions and 52 deletions

View File

@@ -65,7 +65,16 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine {
return false; 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; return true;
} }
@@ -113,19 +122,29 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine {
typeFqn.equals("int") || typeFqn.equals("long") || typeFqn.equals("char"); typeFqn.equals("int") || typeFqn.equals("long") || typeFqn.equals("char");
} }
private boolean isWildcardVariable(String eventStr) { private boolean isDynamicVariable(String eventStr) {
if (eventStr == null) return false; 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(" : ")) { if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
return true; 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; return false;
} }
} }

View File

@@ -54,18 +54,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
List<String> startMethods = new ArrayList<>(); List<String> startMethods = new ArrayList<>();
startMethods.add(startMethod); 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<String> impls = context.getImplementations(ep.getClassName());
for (String impl : impls) {
startMethods.add(impl + "." + ep.getMethodName());
}
}
boolean foundAny = false; boolean foundAny = false;
for (TriggerPoint tp : triggers) { for (TriggerPoint tp : triggers) {
String targetMethod = tp.getClassName() + "." + tp.getMethodName(); String targetMethod = tp.getClassName() + "." + tp.getMethodName();
@@ -1434,24 +1422,23 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
boolean shouldExpand = true; boolean shouldExpand = true;
if (impls.isEmpty()) { if (impls.isEmpty()) {
shouldExpand = false; 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 { } else {
if (td == null && impls.size() > 3) {
shouldExpand = false;
}
if (impls.size() > 20) {
shouldExpand = false;
}
boolean isImplicitThis = node.getExpression() instanceof ThisExpression; 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; shouldExpand = false;
} }
} }
allResolved.add(baseCalled);
if (shouldExpand) { if (shouldExpand) {
for (String impl : impls) { for (String impl : impls) {
allResolved.add(impl + "." + methodName); allResolved.add(impl + "." + methodName);
} }
} else {
allResolved.add(baseCalled);
} }
} else { } else {
allResolved.add(baseCalled); allResolved.add(baseCalled);

View File

@@ -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<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited);
for (List<String> superPath : superPaths) {
List<String> 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<String> impls = context.getImplementations(className);
if (impls != null) {
for (String impl : impls) {
String implMethod = impl + "." + methodName;
if (graph.containsKey(implMethod)) {
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited);
for (List<String> implPath : implPaths) {
List<String> path = new ArrayList<>();
path.add(start);
path.addAll(implPath);
result.add(path);
}
}
}
}
}
visited.remove(start); visited.remove(start);
return result; return result;
} }

View File

@@ -165,7 +165,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
if (receiver instanceof MethodInvocation miReceiver) { if (receiver instanceof MethodInvocation miReceiver) {
String returnType = resolveMethodInvocationReturnType(miReceiver); String returnType = resolveMethodInvocationReturnType(miReceiver);
if (returnType != null) { if (returnType != null) {
return resolveToDeclaringMethod(returnType, methodName); return returnType + "." + methodName;
} }
} }
@@ -189,7 +189,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding); String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
if (concreteFqn != null) { if (concreteFqn != null) {
System.out.println(" -> RESOLVED CONCRETE FQN: " + concreteFqn); System.out.println(" -> RESOLVED CONCRETE FQN: " + concreteFqn);
return resolveToDeclaringMethod(concreteFqn, methodName); return concreteFqn + "." + methodName;
} else { } else {
System.out.println(" -> RESOLVE FAILED, RETURNED NULL"); System.out.println(" -> RESOLVE FAILED, RETURNED NULL");
} }
@@ -223,7 +223,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
} }
if (typeName != null && !typeName.isEmpty()) { 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) { if (receiver instanceof SimpleName sn) {
String fallbackTypeFqn = resolveReceiverTypeFallback(sn); String fallbackTypeFqn = resolveReceiverTypeFallback(sn);
if (fallbackTypeFqn != null) { if (fallbackTypeFqn != null) {
return resolveToDeclaringMethod(fallbackTypeFqn, methodName); return fallbackTypeFqn + "." + methodName;
} }
String receiverName = sn.getIdentifier(); String receiverName = sn.getIdentifier();
return receiverName + "." + methodName; return receiverName + "." + methodName;
@@ -246,21 +246,6 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
return null; 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) { private String resolveReceiverTypeFallback(SimpleName receiverNameNode) {
String varName = receiverNameNode.getIdentifier(); String varName = receiverNameNode.getIdentifier();

View File

@@ -196,7 +196,7 @@ 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()).isNull(); assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
} }
@Test @Test

View File

@@ -162,4 +162,48 @@ class StrictFqnMatchingEngineTest {
assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); 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();
}
} }

View File

@@ -182,14 +182,14 @@ class EnterpriseBugsTest {
public class OrderController implements OrderApi { public class OrderController implements OrderApi {
private OrderService service; private OrderService service;
public void submit() { public void submit() {
service.trigger(); service.trigger("someEvent");
} }
} }
"""; """;
String serviceSrc = """ String serviceSrc = """
package com.example; package com.example;
public class OrderService { public class OrderService {
public void trigger() {} public void trigger(String EVENT) {}
} }
"""; """;