Compare commits
2 Commits
28df9fc99f
...
9bea5c4687
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bea5c4687 | |||
| 475084aed4 |
@@ -6,11 +6,22 @@ import java.util.List;
|
||||
|
||||
public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
|
||||
private final java.util.Map<java.util.Map.Entry<Event, TriggerPoint>, Boolean> matchesCache = new java.util.HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
|
||||
if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
|
||||
return false;
|
||||
}
|
||||
java.util.Map.Entry<Event, TriggerPoint> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(stateMachineEvent, triggerPoint);
|
||||
if (matchesCache.containsKey(cacheKey)) return matchesCache.get(cacheKey);
|
||||
|
||||
boolean result = calculateMatches(stateMachineEvent, triggerPoint);
|
||||
matchesCache.put(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean calculateMatches(Event stateMachineEvent, TriggerPoint triggerPoint) {
|
||||
|
||||
String rawTriggerEvent = triggerPoint.getEvent();
|
||||
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
|
||||
|
||||
@@ -21,6 +21,21 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
protected final VariableTracer variableTracer;
|
||||
protected final ConstantExtractor constantExtractor;
|
||||
protected final ConstructorAnalyzer constructorAnalyzer;
|
||||
private final Map<String, ASTNode> parsedNodeCache = new HashMap<>();
|
||||
|
||||
private ASTNode parseExpressionString(String expr) {
|
||||
if (expr == null) return null;
|
||||
return parsedNodeCache.computeIfAbsent(expr, e -> {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS17);
|
||||
parser.setKind(ASTParser.K_EXPRESSION);
|
||||
parser.setSource(e.toCharArray());
|
||||
try {
|
||||
return parser.createAST(null);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
protected interface RhsResolver {
|
||||
@@ -268,7 +283,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
constantExtractor.extractConstantsFromExpression(tracedSetter, setterEvents);
|
||||
}
|
||||
if (!setterEvents.isEmpty()) {
|
||||
System.out.println("DEBUG Early return 1: setterEvents = " + setterEvents);
|
||||
log.debug("Early return 1: setterEvents = {}", setterEvents);
|
||||
return TriggerPoint.builder()
|
||||
.event(tp.getEvent())
|
||||
.className(tp.getClassName())
|
||||
@@ -293,7 +308,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (hasGetterOnReceiver) {
|
||||
List<String> getterEvents = evaluateGetterOnReceiver(resolvedValue, methodSuffix, entryMethod, path, callGraph);
|
||||
if (getterEvents != null && !getterEvents.isEmpty()) {
|
||||
System.out.println("DEBUG Early return 2: getterEvents = " + getterEvents);
|
||||
log.debug("Early return 2: getterEvents = {}", getterEvents);
|
||||
return TriggerPoint.builder()
|
||||
.event(tp.getEvent())
|
||||
.className(tp.getClassName())
|
||||
@@ -323,7 +338,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("DEBUG resolvedValue before ENUM_SET check: " + resolvedValue + " for path: " + path);
|
||||
log.debug("resolvedValue before ENUM_SET check: {} for path: {}", resolvedValue, path);
|
||||
List<String> polymorphicEvents = new ArrayList<>();
|
||||
|
||||
if (resolvedValue.startsWith("ENUM_SET:")) {
|
||||
@@ -348,10 +363,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.build();
|
||||
}
|
||||
|
||||
ASTParser exprParser = ASTParser.newParser(AST.JLS17);
|
||||
exprParser.setKind(ASTParser.K_EXPRESSION);
|
||||
exprParser.setSource(resolvedValue.toCharArray());
|
||||
ASTNode exprNode = exprParser.createAST(null);
|
||||
ASTNode exprNode = parseExpressionString(resolvedValue);
|
||||
|
||||
String varName = null;
|
||||
String methodName = null;
|
||||
@@ -552,10 +564,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (extractedFinalTraced[0] != null) {
|
||||
resolvedValue = extractedFinalTraced[0] + extractedFinalTraced[1];
|
||||
if (extractedFinalTraced[0].contains("new ") && extractedFinalTraced[0].contains("{")) {
|
||||
ASTParser p = ASTParser.newParser(AST.JLS17);
|
||||
p.setKind(ASTParser.K_EXPRESSION);
|
||||
p.setSource(resolvedValue.toCharArray());
|
||||
ASTNode newNode = p.createAST(null);
|
||||
ASTNode newNode = parseExpressionString(resolvedValue);
|
||||
if (newNode instanceof Expression) {
|
||||
List<Expression> traced = variableTracer.traceVariableAll((Expression) newNode);
|
||||
for (Expression ex : traced) {
|
||||
@@ -581,10 +590,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (polymorphicEvents.isEmpty() && variableTracer != null) {
|
||||
String initializer = variableTracer.traceLocalVariable(methodFqn, varName);
|
||||
if (initializer != null && !initializer.equals(varName)) {
|
||||
ASTParser mapParser = ASTParser.newParser(AST.JLS17);
|
||||
mapParser.setKind(ASTParser.K_EXPRESSION);
|
||||
mapParser.setSource(initializer.toCharArray());
|
||||
ASTNode mapNode = mapParser.createAST(null);
|
||||
ASTNode mapNode = parseExpressionString(initializer);
|
||||
if (mapNode instanceof Expression) {
|
||||
List<Expression> mapTraced = variableTracer.traceVariableAll((Expression) mapNode);
|
||||
for (Expression t : mapTraced) {
|
||||
@@ -625,7 +631,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("DEBUG declaredType before getEnumValues: " + declaredType + " for exprNode: " + exprNode + " in " + entryMethod);
|
||||
log.debug("declaredType before getEnumValues: {} for exprNode: {} in {}", declaredType, exprNode, entryMethod);
|
||||
if (declaredType != null) {
|
||||
List<String> enumValues = context.getEnumValues(declaredType);
|
||||
if (enumValues != null && !enumValues.isEmpty()) {
|
||||
@@ -935,10 +941,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
protected List<String> evaluateGetterOnReceiver(String resolvedValue, String methodSuffix, String entryMethod, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||
ASTParser exprParser = ASTParser.newParser(AST.JLS17);
|
||||
exprParser.setKind(ASTParser.K_EXPRESSION);
|
||||
exprParser.setSource(resolvedValue.toCharArray());
|
||||
ASTNode exprNode = exprParser.createAST(null);
|
||||
ASTNode exprNode = parseExpressionString(resolvedValue);
|
||||
|
||||
if (exprNode instanceof SuperMethodInvocation smi) {
|
||||
String getterName = smi.getName().getIdentifier();
|
||||
@@ -1499,10 +1502,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (arg == null) return null;
|
||||
String current = arg;
|
||||
while (current.contains("(") && !current.startsWith("new ")) {
|
||||
org.eclipse.jdt.core.dom.ASTParser argParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.JLS17);
|
||||
argParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
||||
argParser.setSource(current.toCharArray());
|
||||
org.eclipse.jdt.core.dom.ASTNode argNode = argParser.createAST(null);
|
||||
org.eclipse.jdt.core.dom.ASTNode argNode = parseExpressionString(current);
|
||||
if (argNode instanceof org.eclipse.jdt.core.dom.MethodInvocation miArg && !miArg.arguments().isEmpty()) {
|
||||
boolean shouldUnpack = false;
|
||||
org.eclipse.jdt.core.dom.Expression recv = miArg.getExpression();
|
||||
|
||||
@@ -11,6 +11,9 @@ public class CallGraphPathFinder {
|
||||
private final CodebaseContext context;
|
||||
private final Map<java.util.Map.Entry<String, String>, Boolean> heuristicCache = new HashMap<>();
|
||||
|
||||
private final Map<java.util.Map.Entry<String, String>, List<List<String>>> memoCache = new HashMap<>();
|
||||
private final Map<java.util.Map.Entry<String, String>, Boolean> unreachableCache = new HashMap<>();
|
||||
|
||||
public CallGraphPathFinder(CodebaseContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
@@ -50,16 +53,17 @@ 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}, new HashMap<>());
|
||||
return findAllPaths(start, target, graph, visited, 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, Map<String, List<List<String>>> memo) {
|
||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited, boolean[] parentHitCycle) {
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
if (unreachable.contains(start)) {
|
||||
java.util.Map.Entry<String, String> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(start, target);
|
||||
if (unreachableCache.containsKey(cacheKey)) {
|
||||
return result;
|
||||
}
|
||||
if (memo.containsKey(start)) {
|
||||
List<List<String>> cached = memo.get(start);
|
||||
if (memoCache.containsKey(cacheKey)) {
|
||||
List<List<String>> cached = memoCache.get(cacheKey);
|
||||
for (List<String> path : cached) {
|
||||
boolean hasCycle = false;
|
||||
for (String node : path) {
|
||||
@@ -95,7 +99,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, unreachable, localHitCycle, memo);
|
||||
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited, localHitCycle);
|
||||
for (List<String> subPath : subPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
@@ -119,7 +123,7 @@ public class CallGraphPathFinder {
|
||||
if (superclass != null) {
|
||||
String superMethod = superclass + "." + methodName;
|
||||
if (graph.containsKey(superMethod)) {
|
||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited, unreachable, localHitCycle, memo);
|
||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited, localHitCycle);
|
||||
for (List<String> superPath : superPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
@@ -143,7 +147,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, unreachable, localHitCycle, memo);
|
||||
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited, localHitCycle);
|
||||
for (List<String> implPath : implPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
@@ -159,13 +163,13 @@ public class CallGraphPathFinder {
|
||||
if (localHitCycle[0]) {
|
||||
parentHitCycle[0] = true;
|
||||
} else if (result.isEmpty()) {
|
||||
unreachable.add(start);
|
||||
unreachableCache.put(cacheKey, true);
|
||||
} else {
|
||||
List<List<String>> toCache = new ArrayList<>();
|
||||
for (List<String> path : result) {
|
||||
toCache.add(new ArrayList<>(path));
|
||||
}
|
||||
memo.put(start, toCache);
|
||||
memoCache.put(cacheKey, toCache);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -89,7 +89,8 @@ public class ConstantExtractor {
|
||||
} else if (expr instanceof MethodInvocation mi) {
|
||||
String methodName = mi.getName().getIdentifier();
|
||||
|
||||
System.out.println("Processing mi: " + mi); if (methodName.equals("get") || methodName.equals("getOrDefault")) {
|
||||
log.trace("Processing mi: {}", mi);
|
||||
if (methodName.equals("get") || methodName.equals("getOrDefault")) {
|
||||
if (mi.getExpression() != null) {
|
||||
if (variableTracer != null) {
|
||||
List<Expression> traced = variableTracer.traceVariableAll(mi.getExpression());
|
||||
@@ -281,7 +282,7 @@ public class ConstantExtractor {
|
||||
}
|
||||
|
||||
public List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
||||
System.out.println("DEBUG ConstantExtractor.resolveMethodReturnConstant CALLED: className=" + className + ", methodName=" + methodName);
|
||||
log.debug("ConstantExtractor.resolveMethodReturnConstant CALLED: className={}, methodName={}", className, methodName);
|
||||
log.debug("Entering resolveMethodReturnConstant with className={}, methodName={}, depth={}", className, methodName, depth);
|
||||
if (depth > 20) {
|
||||
log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName);
|
||||
@@ -392,7 +393,7 @@ public class ConstantExtractor {
|
||||
}
|
||||
}
|
||||
visited.remove(fqn);
|
||||
System.out.println("DEBUG resolveMethodReturnConstant className=" + className + " methodName=" + methodName + " returns: " + constants);
|
||||
log.debug("resolveMethodReturnConstant className={} methodName={} returns: {}", className, methodName, constants);
|
||||
log.debug("resolveMethodReturnConstant for class={}, method={} resolved constants: {}", className, methodName, constants);
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ConstructorAnalyzer {
|
||||
private final CodebaseContext context;
|
||||
private final ConstantResolver constantResolver;
|
||||
@@ -591,11 +594,11 @@ public class ConstructorAnalyzer {
|
||||
|
||||
try {
|
||||
Map<String, String> fieldValues = buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor);
|
||||
System.out.println("RESOLVE_GETTER: td=" + context.getFqn(td) + ", fieldValues=" + fieldValues);
|
||||
log.debug("RESOLVE_GETTER: td={}, fieldValues={}", context.getFqn(td), fieldValues);
|
||||
if (fieldValues.isEmpty()) return Collections.emptyList();
|
||||
|
||||
String result = constantResolver.evaluateMethodBodyWithLocals(getter, fieldValues, context, visited);
|
||||
System.out.println("RESOLVE_GETTER: result=" + result);
|
||||
log.debug("RESOLVE_GETTER: result={}", result);
|
||||
return result != null ? List.of(result) : Collections.emptyList();
|
||||
} finally {
|
||||
visited.remove(getterKey);
|
||||
|
||||
@@ -185,16 +185,16 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
nameBinding = fa.resolveFieldBinding();
|
||||
}
|
||||
if (nameBinding instanceof IVariableBinding varBinding) {
|
||||
System.out.println("CALLGRAPH RESOLVING BEAN FOR BINDING: " + varBinding.getName() + " calling " + methodName);
|
||||
log.debug("CALLGRAPH RESOLVING BEAN FOR BINDING: {} calling {}", varBinding.getName(), methodName);
|
||||
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
||||
if (concreteFqn != null) {
|
||||
System.out.println(" -> RESOLVED CONCRETE FQN: " + concreteFqn);
|
||||
log.debug(" -> RESOLVED CONCRETE FQN: {}", concreteFqn);
|
||||
return concreteFqn + "." + methodName;
|
||||
} else {
|
||||
System.out.println(" -> RESOLVE FAILED, RETURNED NULL");
|
||||
log.debug(" -> RESOLVE FAILED, RETURNED NULL");
|
||||
}
|
||||
} else {
|
||||
System.out.println("CALLGRAPH NAME BINDING IS NOT VARIABLE: " + nameBinding + " calling " + methodName);
|
||||
log.debug("CALLGRAPH NAME BINDING IS NOT VARIABLE: {} calling {}", nameBinding, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SpringDependencyResolver {
|
||||
private final SpringBeanRegistry registry;
|
||||
|
||||
@@ -21,17 +24,17 @@ public class SpringDependencyResolver {
|
||||
*/
|
||||
public List<SpringBean> resolve(String requiredTypeFqn, String qualifier, String injectionName) {
|
||||
if (requiredTypeFqn == null) return new ArrayList<>();
|
||||
log.debug("RESOLVING {} qual={} injectionName={}", requiredTypeFqn, qualifier, injectionName);
|
||||
|
||||
// 1. Filter by Type (Exact FQN or Assignable Type)
|
||||
System.out.println("RESOLVING " + requiredTypeFqn + " qual=" + qualifier + " injectionName=" + injectionName);
|
||||
List<SpringBean> candidates = registry.getBeans().stream()
|
||||
.filter(bean -> requiredTypeFqn.equals(bean.getTypeFqn()) || bean.getAssignableTypes().contains(requiredTypeFqn))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println("CANDIDATES AFTER TYPE: " + candidates.stream().map(c -> c.getTypeFqn() + " (primary=" + c.isPrimary() + " names=" + c.getBeanNames() + " qual=" + c.getQualifiers() + ")").collect(Collectors.toList()));
|
||||
log.debug("CANDIDATES AFTER TYPE: {}", candidates.stream().map(c -> c.getTypeFqn() + " (primary=" + c.isPrimary() + " names=" + c.getBeanNames() + " qual=" + c.getQualifiers() + ")").collect(Collectors.toList()));
|
||||
if (candidates.isEmpty() || candidates.size() == 1) {
|
||||
System.out.println("RETURNING: " + candidates.size());
|
||||
return candidates;
|
||||
log.debug("RETURNING: {}", candidates.size());
|
||||
return candidates;
|
||||
}
|
||||
|
||||
// 2. Filter by Qualifier (if provided)
|
||||
@@ -81,7 +84,7 @@ public class SpringDependencyResolver {
|
||||
}
|
||||
|
||||
// Return whatever candidates remain (could be ambiguous)
|
||||
System.out.println("RETURNING: " + candidates.size());
|
||||
log.debug("RETURNING: {}", candidates.size());
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ public class CodebaseContext {
|
||||
String fqn = simpleNameToFqn.get(cleanName);
|
||||
if (fqn != null) values = enumValues.get(fqn);
|
||||
}
|
||||
System.out.println("DEBUG getEnumValues called for: " + fqnOrSimpleName + " resolved to " + cleanName + ". Returns: " + values);
|
||||
log.debug("getEnumValues called for: {} resolved to {}. Returns: {}", fqnOrSimpleName, cleanName, values);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ package click.kamil.springstatemachineexporter.ast.common;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class JdtDataFlowModel implements DataFlowModel {
|
||||
private final CodebaseContext context;
|
||||
private final Map<MethodDeclaration, ControlFlowGraph> cfgCache = new HashMap<>();
|
||||
@@ -436,7 +439,8 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
currentParamValues.put(paramVb, resolvedArg);
|
||||
}
|
||||
}
|
||||
System.out.println("EVAL_CTOR: cd=" + cd.getName() + ", currentParamValues=" + currentParamValues);
|
||||
|
||||
log.debug("EVAL_CTOR: cd={}, currentParamValues={}", cd.getName(), currentParamValues);
|
||||
|
||||
List<?> statements = cd.getBody().statements();
|
||||
if (!statements.isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user