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;
}
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;
}
}

View File

@@ -54,18 +54,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
List<String> 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<String> 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);

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);
return result;
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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) {}
}
""";