Compare commits
3 Commits
180a283383
...
7807df57ca
| Author | SHA1 | Date | |
|---|---|---|---|
| 7807df57ca | |||
| 82719489aa | |||
| c390f02389 |
BIN
TestHeuristic.class
Normal file
BIN
TestHeuristic.class
Normal file
Binary file not shown.
17
TestHeuristic.java
Normal file
17
TestHeuristic.java
Normal file
@@ -0,0 +1,17 @@
|
||||
import java.util.*;
|
||||
|
||||
public class TestHeuristic {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(isFullyQualifiedMethod("TransformerImpl.transform"));
|
||||
System.out.println(isFullyQualifiedMethod("Transformer.transform"));
|
||||
}
|
||||
|
||||
private static boolean isFullyQualifiedMethod(String methodFqn) {
|
||||
if (methodFqn == null || !methodFqn.contains(".")) return false;
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
String classPart = methodFqn.substring(0, lastDot);
|
||||
if (classPart.contains(".")) return true;
|
||||
if (!classPart.isEmpty() && Character.isUpperCase(classPart.charAt(0))) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -194,20 +194,38 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
}
|
||||
}
|
||||
|
||||
private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
|
||||
private final java.util.Map<String, Boolean> guardedCache = new java.util.HashMap<>();
|
||||
private static final java.util.regex.Pattern SUFFIX_PATTERN = java.util.regex.Pattern.compile("(?i)(.+)(Event|Action|Transition|Command)(s)?$");
|
||||
private static final java.util.regex.Pattern FQN_PATTERN = java.util.regex.Pattern.compile("^.*\\.([A-Z0-9_]+)$");
|
||||
|
||||
private String simplify(String name) {
|
||||
if (name == null) return null;
|
||||
if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
|
||||
|
||||
// Strip common suffixes
|
||||
String simplified = name.replaceAll("(?i)(.+)(Event|Action|Transition|Command)(s)?$", "$1");
|
||||
String simplified = SUFFIX_PATTERN.matcher(name).replaceAll("$1");
|
||||
if (simplified.isEmpty()) {
|
||||
simplified = name;
|
||||
}
|
||||
// Simplify full identifiers to just the last part (enum name)
|
||||
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
|
||||
// For remaining full caps with underscores (like EVENT_X), keep as is or try to simplify
|
||||
simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
|
||||
|
||||
simplifyCache.put(name, simplified);
|
||||
return simplified;
|
||||
}
|
||||
|
||||
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
||||
if (smEvent == null || triggerEvent == null) return false;
|
||||
String cacheKey = smEvent + "|" + triggerEvent;
|
||||
if (guardedCache.containsKey(cacheKey)) return guardedCache.get(cacheKey);
|
||||
|
||||
boolean result = calculateGuardedContains(smEvent, triggerEvent);
|
||||
guardedCache.put(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean calculateGuardedContains(String smEvent, String triggerEvent) {
|
||||
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
||||
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||
@@ -225,7 +243,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
// Guard: If the matching part is very short (< 4 chars), it must be bounded by word separators
|
||||
// to avoid matching completely unrelated words (e.g., "PAY" matching "PAYMENT" or "E" matching "UPDATE")
|
||||
if (shorter.length() < 4) {
|
||||
return longer.matches(".*(^|_|-)" + shorter + "(_|-|$).*");
|
||||
return longer.matches(".*(^|_|-)" + java.util.regex.Pattern.quote(shorter) + "(_|-|$).*");
|
||||
}
|
||||
// For longer strings, a straight contains is usually safe enough
|
||||
return true;
|
||||
|
||||
@@ -87,8 +87,22 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
return isGuardedContains(smEvent, triggerEvent);
|
||||
}
|
||||
|
||||
private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
|
||||
private final java.util.Map<String, Boolean> guardedCache = new java.util.HashMap<>();
|
||||
private static final java.util.regex.Pattern SUFFIX_PATTERN = java.util.regex.Pattern.compile("(?i)(.+)(Event|Action|Transition|Command)(s)?$");
|
||||
private static final java.util.regex.Pattern FQN_PATTERN = java.util.regex.Pattern.compile("^.*\\.([A-Z0-9_]+)$");
|
||||
|
||||
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
||||
if (smEvent == null || triggerEvent == null) return false;
|
||||
String cacheKey = smEvent + "|" + triggerEvent;
|
||||
if (guardedCache.containsKey(cacheKey)) return guardedCache.get(cacheKey);
|
||||
|
||||
boolean result = calculateGuardedContains(smEvent, triggerEvent);
|
||||
guardedCache.put(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean calculateGuardedContains(String smEvent, String triggerEvent) {
|
||||
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
||||
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||
@@ -137,11 +151,15 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
|
||||
private String simplify(String name) {
|
||||
if (name == null) return null;
|
||||
String simplified = name.replaceAll("(?i)(.+)(Event|Action|Transition|Command)(s)?$", "$1");
|
||||
if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
|
||||
|
||||
String simplified = SUFFIX_PATTERN.matcher(name).replaceAll("$1");
|
||||
if (simplified.isEmpty()) {
|
||||
simplified = name;
|
||||
}
|
||||
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
|
||||
simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
|
||||
|
||||
simplifyCache.put(name, simplified);
|
||||
return simplified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.*;
|
||||
@Slf4j
|
||||
public class CallGraphPathFinder {
|
||||
private final CodebaseContext context;
|
||||
private final Map<String, Boolean> heuristicCache = new HashMap<>();
|
||||
|
||||
public CallGraphPathFinder(CodebaseContext context) {
|
||||
this.context = context;
|
||||
@@ -49,15 +50,25 @@ public class CallGraphPathFinder {
|
||||
}
|
||||
|
||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
||||
return findAllPaths(start, target, graph, visited, new HashSet<>(), new boolean[]{false});
|
||||
}
|
||||
|
||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited, Set<String> unreachable, boolean[] parentHitCycle) {
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
if (unreachable.contains(start)) {
|
||||
return result;
|
||||
}
|
||||
if (start.equals(target)) {
|
||||
result.add(new ArrayList<>(List.of(start)));
|
||||
return result;
|
||||
}
|
||||
if (!visited.add(start)) {
|
||||
parentHitCycle[0] = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean[] localHitCycle = new boolean[]{false};
|
||||
|
||||
List<CallEdge> neighbors = graph.get(start);
|
||||
if (neighbors != null) {
|
||||
for (CallEdge edge : neighbors) {
|
||||
@@ -67,7 +78,7 @@ public class CallGraphPathFinder {
|
||||
List<String> path = new ArrayList<>(List.of(start, target));
|
||||
result.add(path);
|
||||
} else {
|
||||
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited);
|
||||
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited, unreachable, localHitCycle);
|
||||
for (List<String> subPath : subPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
@@ -91,7 +102,7 @@ public class CallGraphPathFinder {
|
||||
if (superclass != null) {
|
||||
String superMethod = superclass + "." + methodName;
|
||||
if (graph.containsKey(superMethod)) {
|
||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited);
|
||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited, unreachable, localHitCycle);
|
||||
for (List<String> superPath : superPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
@@ -115,7 +126,7 @@ public class CallGraphPathFinder {
|
||||
for (String impl : impls) {
|
||||
String implMethod = impl + "." + methodName;
|
||||
if (graph.containsKey(implMethod)) {
|
||||
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited);
|
||||
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited, unreachable, localHitCycle);
|
||||
for (List<String> implPath : implPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
@@ -127,6 +138,13 @@ public class CallGraphPathFinder {
|
||||
}
|
||||
}
|
||||
visited.remove(start);
|
||||
|
||||
if (localHitCycle[0]) {
|
||||
parentHitCycle[0] = true;
|
||||
} else if (result.isEmpty()) {
|
||||
unreachable.add(start);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -151,6 +169,19 @@ public class CallGraphPathFinder {
|
||||
if (neighbor == null || target == null) return false;
|
||||
if (neighbor.equals(target)) return true;
|
||||
|
||||
String cacheKey = neighbor + "|" + target;
|
||||
Boolean cached = heuristicCache.get(cacheKey);
|
||||
if (cached != null) return cached;
|
||||
|
||||
boolean result = calculateHeuristicMatch(neighbor, target);
|
||||
heuristicCache.put(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean calculateHeuristicMatch(String neighbor, String target) {
|
||||
System.out.println("calculateHeuristicMatch: neighbor=" + neighbor + ", target=" + target);
|
||||
if (neighbor.equals(target)) return true;
|
||||
|
||||
if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) {
|
||||
int lastDotNeighbor = neighbor.lastIndexOf('.');
|
||||
int lastDotTarget = target.lastIndexOf('.');
|
||||
@@ -172,7 +203,6 @@ public class CallGraphPathFinder {
|
||||
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||
|
||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||
if (simpleClassNeighbor.contains(simpleClassTarget) || simpleClassTarget.contains(simpleClassNeighbor)) return true;
|
||||
|
||||
List<String> impls = context.getImplementations(classTarget);
|
||||
if (impls != null) {
|
||||
@@ -192,7 +222,21 @@ public class CallGraphPathFinder {
|
||||
|
||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
||||
return simpleNeighbor.equals(simpleTarget);
|
||||
if (!simpleNeighbor.equals(simpleTarget)) return false;
|
||||
|
||||
String classNeighbor = neighbor.contains(".") ? neighbor.substring(0, neighbor.lastIndexOf('.')) : null;
|
||||
String classTarget = target.contains(".") ? target.substring(0, target.lastIndexOf('.')) : null;
|
||||
String simpleClassTarget = classTarget != null && classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||
String simpleClassNeighbor = classNeighbor != null && classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||
|
||||
if (simpleClassNeighbor != null && simpleClassTarget != null) {
|
||||
if (simpleClassNeighbor.equalsIgnoreCase(simpleClassTarget)) return true;
|
||||
// e.g. "this" vs "com.example.Machine"
|
||||
if (simpleClassNeighbor.equals("this") || simpleClassNeighbor.equals("super")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isFullyQualifiedMethod(String methodFqn) {
|
||||
|
||||
@@ -525,14 +525,39 @@ public class VariableTracer {
|
||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||
|
||||
List<String> impls = context.getImplementations(classTarget);
|
||||
if (impls != null && impls.contains(classNeighbor)) return true;
|
||||
if (impls != null) {
|
||||
for (String impl : impls) {
|
||||
if (impl.equals(classNeighbor) || impl.endsWith("." + classNeighbor)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> implsN = context.getImplementations(classNeighbor);
|
||||
if (implsN != null && implsN.contains(classTarget)) return true;
|
||||
if (implsN != null) {
|
||||
for (String impl : implsN) {
|
||||
if (impl.equals(classTarget) || impl.endsWith("." + classTarget)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
||||
if (!simpleNeighbor.equals(simpleTarget)) return false;
|
||||
|
||||
String classNeighbor = neighbor.contains(".") ? neighbor.substring(0, neighbor.lastIndexOf('.')) : null;
|
||||
String classTarget = target.contains(".") ? target.substring(0, target.lastIndexOf('.')) : null;
|
||||
String simpleClassTarget = classTarget != null && classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||
String simpleClassNeighbor = classNeighbor != null && classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||
|
||||
if (simpleClassNeighbor != null && simpleClassTarget != null) {
|
||||
if (simpleClassNeighbor.equalsIgnoreCase(simpleClassTarget)) return true;
|
||||
if (simpleClassNeighbor.equals("this") || simpleClassNeighbor.equals("super")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||
ASTNode parent = node.getParent();
|
||||
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
||||
|
||||
Reference in New Issue
Block a user