This commit is contained in:
2026-06-28 15:17:16 +02:00
parent f3d030b19a
commit 4d9fee716b
24 changed files with 2691 additions and 156 deletions

View File

@@ -17,12 +17,12 @@ import java.util.stream.Collectors;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.HeuristicEventMatchingEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine;
public class TransitionLinkerEnricher implements AnalysisEnricher {
private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine();
private final EventMatchingEngine matchingEngine = new HeuristicEventMatchingEngine();
private final EventMatchingEngine matchingEngine = new StrictFqnMatchingEngine();
@Override
public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) {
@@ -59,7 +59,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
.targetState(smSourceRaw)
.event(smEventRaw)
.build();
if (isRoutedToCorrectMachine(chain, result.getName(), context)) {
if (isRoutedToCorrectMachine(chain, result.getName(), context)
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
matched.add(mt);
}
} else {
@@ -70,7 +71,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
.targetState(targetRaw)
.event(smEventRaw)
.build();
if (isRoutedToCorrectMachine(chain, result.getName(), context)) {
if (isRoutedToCorrectMachine(chain, result.getName(), context)
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
matched.add(mt);
}
}
@@ -104,7 +106,26 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName, context);
}
private String simplify(String name) {
private boolean isConstraintCompatible(String constraint, String machineName) {
if (constraint == null || machineName == null) return true;
String cleanMachine = machineName.substring(machineName.lastIndexOf('.') + 1).toUpperCase();
String constraintUpper = constraint.toUpperCase();
java.util.List<String> allMachines = java.util.List.of("ORDER", "DOCUMENT", "USER");
for (String m : allMachines) {
if (!m.equals(cleanMachine)) {
if (constraintUpper.contains(m) && !constraintUpper.contains("!" + m) && !constraintUpper.contains("NOT")) {
if (constraintUpper.contains("\"" + m + "\"") || constraintUpper.contains("'" + m + "'") || constraintUpper.contains(m + ".")) {
return false;
}
}
}
}
return true;
}
private String simplify(String name) {
if (name == null) return null;
// Strip common suffixes
String simplified = name.replaceAll("(?i)(.*)(Event|Action|Transition|Command)(s)?$", "$1");

View File

@@ -0,0 +1,82 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import java.util.List;
public class StrictFqnMatchingEngine implements EventMatchingEngine {
@Override
public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
return false;
}
if (triggerPoint.isExternal()) {
return true;
}
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
String rawTriggerEvent = triggerPoint.getEvent();
if (rawTriggerEvent != null && rawTriggerEvent.startsWith("<SYMBOLIC: ") && rawTriggerEvent.endsWith(".*>")) {
String targetType = rawTriggerEvent.substring(11, rawTriggerEvent.length() - 3);
if (smEventRaw.startsWith(targetType + ".")) {
return true;
}
if (smEventRaw.contains(".")) {
String smType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
if (smType.endsWith("." + targetType) || smType.equals(targetType)) {
return true;
}
}
return false;
}
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
for (String pe : polyEvents) {
if (pe.equals(smEventRaw)) {
return true;
}
if (pe.startsWith("<SYMBOLIC: ") && pe.endsWith(".*>")) {
String targetType = pe.substring(11, pe.length() - 3);
if (smEventRaw.startsWith(targetType + ".")) {
return true;
}
if (smEventRaw.contains(".")) {
String smType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
if (smType.endsWith("." + targetType) || smType.equals(targetType)) {
return true;
}
}
}
}
if (polyEvents.isEmpty() && isWildcardVariable(rawTriggerEvent)) {
return true;
}
return rawTriggerEvent.equals(smEventRaw);
}
private boolean isWildcardVariable(String eventStr) {
if (eventStr == null) return false;
if (eventStr.equals("event") || eventStr.equals("e") ||
eventStr.equals("msg") || eventStr.equals("message") ||
eventStr.equals("payload")) {
return true;
}
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
return true;
}
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
return true;
}
return false;
}
}

View File

@@ -13,8 +13,13 @@ public class CallEdge {
private String targetMethod;
private List<String> arguments;
private String receiver;
private String constraint;
public CallEdge(String targetMethod, List<String> arguments) {
this(targetMethod, arguments, null);
this(targetMethod, arguments, null, null);
}
public CallEdge(String targetMethod, List<String> arguments, String receiver) {
this(targetMethod, arguments, receiver, null);
}
}

View File

@@ -25,4 +25,101 @@ public class TriggerPoint {
@com.fasterxml.jackson.annotation.JsonIgnore
private final String eventTypeFqn; // Type of Event (e.g. OrderEvents FQN)
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
private final boolean external;
private final String constraint;
public TriggerPoint(
String event,
String className,
String methodName,
String sourceFile,
String sourceModule,
String stateMachineId,
String sourceState,
int lineNumber,
String stateTypeFqn,
String eventTypeFqn,
java.util.List<String> polymorphicEvents,
boolean external,
String constraint) {
this.className = className;
this.methodName = methodName;
this.sourceFile = sourceFile;
this.sourceModule = sourceModule;
this.stateMachineId = stateMachineId;
this.sourceState = sourceState;
this.lineNumber = lineNumber;
this.stateTypeFqn = stateTypeFqn;
this.eventTypeFqn = eventTypeFqn;
this.event = qualifyEvent(event, eventTypeFqn);
if (polymorphicEvents != null) {
this.polymorphicEvents = polymorphicEvents.stream()
.map(pe -> qualifyEvent(pe, eventTypeFqn))
.collect(java.util.stream.Collectors.toList());
} else {
this.polymorphicEvents = null;
}
this.external = external;
this.constraint = constraint;
}
private static String qualifyEvent(String e, String eventTypeFqn) {
if (e == null || eventTypeFqn == null || e.isEmpty()) {
return e;
}
if (e.startsWith("<SYMBOLIC: ")) {
return e;
}
if (isWildcardVariable(e)) {
return e;
}
if (Character.isLowerCase(e.charAt(0))) {
return e;
}
if (!isEnumConstantCandidate(e)) {
return e;
}
if (e.contains(".")) {
String qualifier = e.substring(0, e.lastIndexOf('.'));
String lastSegment = e.substring(e.lastIndexOf('.') + 1);
if (qualifier.equals(eventTypeFqn)) {
return e;
}
if (eventTypeFqn.endsWith("." + qualifier) || eventTypeFqn.equals(qualifier)) {
return eventTypeFqn + "." + lastSegment;
}
return e;
}
if (eventTypeFqn.startsWith("java.lang.") || eventTypeFqn.equals("int") || eventTypeFqn.equals("long") || eventTypeFqn.equals("char")) {
return e;
}
return eventTypeFqn + "." + e;
}
private static boolean isEnumConstantCandidate(String eventStr) {
if (eventStr == null) return false;
if (eventStr.startsWith("<SYMBOLIC: ")) {
return false;
}
if (eventStr.contains(" ") || eventStr.contains("(") || eventStr.contains(")") || eventStr.contains("?")) {
return false;
}
return true;
}
private static boolean isWildcardVariable(String eventStr) {
if (eventStr == null) return false;
if (eventStr.equals("event") || eventStr.equals("e") ||
eventStr.equals("msg") || eventStr.equals("message") ||
eventStr.equals("payload")) {
return true;
}
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
return true;
}
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
return true;
}
return false;
}
}

View File

@@ -99,10 +99,16 @@ public class ConstantResolver {
}
}
if ("valueOf".equals(mi.getName().getIdentifier()) && mi.getExpression() != null) {
java.util.List<String> enumValues = context.getEnumValues(mi.getExpression().toString());
if (enumValues != null && !enumValues.isEmpty()) {
return "ENUM_SET:" + String.join(",", enumValues);
if ("valueOf".equals(mi.getName().getIdentifier())) {
String enumFqn = null;
if (mi.arguments().size() == 2) {
enumFqn = resolveEnumFqn((Expression) mi.arguments().get(0), context);
} else if (mi.arguments().size() == 1 && mi.getExpression() != null) {
enumFqn = resolveEnumFqn(mi.getExpression(), context);
}
if (enumFqn != null) {
return "<SYMBOLIC: " + enumFqn + ".*>";
}
}
@@ -648,4 +654,40 @@ public class ConstantResolver {
}
return null;
}
private String resolveEnumFqn(Expression expr, CodebaseContext context) {
if (expr == null) return null;
if (expr instanceof TypeLiteral tl) {
ITypeBinding binding = tl.getType().resolveBinding();
if (binding != null) {
return binding.getQualifiedName();
}
String typeName = tl.getType().toString();
if (typeName.endsWith(".class")) {
typeName = typeName.substring(0, typeName.length() - 6);
}
TypeDeclaration td = context.getTypeDeclaration(typeName);
if (td != null) return context.getFqn(td);
return typeName;
}
ITypeBinding binding = expr.resolveTypeBinding();
if (binding != null) {
if (binding.isClass() && "java.lang.Class".equals(binding.getErasure().getQualifiedName())) {
ITypeBinding[] typeArgs = binding.getTypeArguments();
if (typeArgs.length > 0) {
return typeArgs[0].getQualifiedName();
}
}
return binding.getQualifiedName();
}
String exprStr = expr.toString();
if (exprStr.endsWith(".class")) {
exprStr = exprStr.substring(0, exprStr.length() - 6);
}
TypeDeclaration td = context.getTypeDeclaration(exprStr);
if (td != null) return context.getFqn(td);
return exprStr;
}
}

View File

@@ -75,11 +75,38 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
}
protected TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
if (path.size() < 2) return tp;
if (path.size() < 2) {
boolean isExternal = isExternalParameter(tp.getClassName() + "." + tp.getMethodName(), tp.getEvent());
return tp.toBuilder().external(isExternal).constraint(tp.getConstraint()).build();
}
String event = tp.getEvent();
if (event == null || event.isEmpty() || event.contains("\"")) return tp;
if (event == null || event.isEmpty() || event.contains("\"")) {
return tp;
}
String[] finalParamNameRef = { event };
TriggerPoint resolved = resolveTriggerPointParametersOriginal(tp, path, callGraph, finalParamNameRef);
if (resolved == null) return null;
String entryMethod = path.isEmpty() ? (tp.getClassName() + "." + tp.getMethodName()) : path.get(0);
boolean isExternal = isExternalParameter(entryMethod, finalParamNameRef[0]);
String pathConstraint = extractPathConstraints(path, callGraph);
String triggerConstraint = resolved.getConstraint();
String finalConstraint = null;
if (pathConstraint != null && triggerConstraint != null) {
finalConstraint = pathConstraint + " && " + triggerConstraint;
} else if (pathConstraint != null) {
finalConstraint = pathConstraint;
} else {
finalConstraint = triggerConstraint;
}
return resolved.toBuilder().external(isExternal).constraint(finalConstraint).build();
}
protected TriggerPoint resolveTriggerPointParametersOriginal(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph, String[] finalParamNameRef) {
String event = tp.getEvent();
String currentParamName = event;
String resolvedValue = event;
String methodSuffix = "";
@@ -87,6 +114,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix);
currentParamName = extractedEntry[0];
methodSuffix = extractedEntry[1];
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
for (int i = path.size() - 1; i > 0; i--) {
String target = path.get(i);
@@ -156,13 +184,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
}
// Intentionally not replacing 'super' because super should remain statically bound to the superclass of where the code is defined.
}
String[] extractedArg = extractMethodSuffix(arg, methodSuffix);
arg = extractedArg[0];
methodSuffix = extractedArg[1];
currentParamName = arg;
resolvedValue = arg + methodSuffix;
found = true;
break;
String[] extractedArg = extractMethodSuffix(arg, methodSuffix);
arg = extractedArg[0];
methodSuffix = extractedArg[1];
currentParamName = arg;
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
resolvedValue = arg + methodSuffix;
found = true;
break;
}
}
}
@@ -194,6 +223,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(setterEvents)
.constraint(tp.getConstraint())
.build();
}
}
@@ -214,6 +244,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(getterEvents)
.constraint(tp.getConstraint())
.build();
}
}
@@ -224,6 +255,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
tracedVar = extractedFinalTraced[0];
methodSuffix = extractedFinalTraced[1];
currentParamName = tracedVar;
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
resolvedValue = tracedVar + methodSuffix;
}
}
@@ -245,6 +277,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.build();
}
@@ -363,6 +396,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceState(tp.getSourceState())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.build();
}
}
@@ -598,6 +632,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.sourceFile(tp.getSourceFile())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents)
.constraint(tp.getConstraint())
.build();
}
@@ -1022,6 +1057,55 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
return constantExtractor.resolveClassConstantReturns(className, context, contextCu);
}
private boolean isExternalParameter(String entryMethod, String paramName) {
if (entryMethod == null || paramName == null || !entryMethod.contains(".")) return false;
String className = entryMethod.substring(0, entryMethod.lastIndexOf('.'));
String methodName = entryMethod.substring(entryMethod.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null) {
for (Object paramObj : md.parameters()) {
if (paramObj instanceof SingleVariableDeclaration svd) {
if (svd.getName().getIdentifier().equals(paramName)) {
for (Object modifier : svd.modifiers()) {
if (modifier instanceof Annotation annotation) {
String typeName = annotation.getTypeName().getFullyQualifiedName();
if (typeName.endsWith("PathVariable") || typeName.endsWith("RequestBody") || typeName.endsWith("RequestParam")
|| typeName.endsWith("PathParam") || typeName.endsWith("QueryParam")) {
return true;
}
}
}
}
}
}
}
}
return false;
}
private String extractPathConstraints(List<String> path, Map<String, List<CallEdge>> callGraph) {
List<String> constraints = new ArrayList<>();
for (int i = 0; i < path.size() - 1; i++) {
String caller = path.get(i);
String target = path.get(i + 1);
List<CallEdge> edges = callGraph.get(caller);
if (edges != null) {
for (CallEdge edge : edges) {
if (edge.getTargetMethod().equals(target) || pathFinder.isHeuristicMatch(edge.getTargetMethod(), target)) {
if (edge.getConstraint() != null && !edge.getConstraint().isEmpty()) {
constraints.add(edge.getConstraint());
break;
}
}
}
}
}
if (constraints.isEmpty()) return null;
return String.join(" && ", constraints);
}
protected abstract Map<String, List<CallEdge>> buildCallGraph();
protected abstract String resolveCalledMethod(MethodInvocation node);
}

View File

@@ -70,7 +70,14 @@ public class GenericEventDetector {
String sourceState = extractSourceState(node);
String[] smTypes = resolveStateMachineTypeArgumentsForExpression(emr.getExpression(), node);
boolean external = false;
if (receiver != null) {
AssignmentDag dag = new AssignmentDag(method);
external = dag.isExternal(receiver, new java.util.HashSet<>());
}
if (eventValue != null) {
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
triggers.add(TriggerPoint.builder()
.event(eventValue)
.sourceState(sourceState)
@@ -80,6 +87,8 @@ public class GenericEventDetector {
.lineNumber(cu.getLineNumber(node.getStartPosition()))
.stateTypeFqn(smTypes[0])
.eventTypeFqn(smTypes[1])
.external(external)
.constraint(constraint)
.build());
}
}
@@ -227,6 +236,15 @@ public class GenericEventDetector {
String sourceState = extractSourceState(node);
String[] smTypes = resolveStateMachineTypeArguments(node);
boolean external = false;
if (!node.arguments().isEmpty()) {
Expression eventExpr = (Expression) node.arguments().get(0);
AssignmentDag dag = new AssignmentDag(method);
external = dag.isExternal(eventExpr, new java.util.HashSet<>());
}
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
List<TriggerPoint> results = new ArrayList<>();
if (eventValue != null && eventValue.startsWith("ENUM_SET:")) {
@@ -242,6 +260,8 @@ public class GenericEventDetector {
.lineNumber(cu.getLineNumber(node.getStartPosition()))
.stateTypeFqn(smTypes[0])
.eventTypeFqn(smTypes[1])
.external(external)
.constraint(constraint)
.build());
}
}
@@ -255,6 +275,8 @@ public class GenericEventDetector {
.lineNumber(cu.getLineNumber(node.getStartPosition()))
.stateTypeFqn(smTypes[0])
.eventTypeFqn(smTypes[1])
.external(external)
.constraint(constraint)
.build());
}
@@ -635,4 +657,93 @@ public class GenericEventDetector {
return simpleName;
}
private static class AssignmentDag {
final java.util.Map<String, List<Expression>> assignments = new java.util.HashMap<>();
final java.util.Set<SingleVariableDeclaration> parameters = new java.util.HashSet<>();
AssignmentDag(MethodDeclaration method) {
if (method == null) return;
for (Object paramObj : method.parameters()) {
if (paramObj instanceof SingleVariableDeclaration svd) {
parameters.add(svd);
}
}
if (method.getBody() != null) {
method.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment fragment) {
String varName = fragment.getName().getIdentifier();
if (fragment.getInitializer() != null) {
assignments.computeIfAbsent(varName, k -> new ArrayList<>()).add(fragment.getInitializer());
}
return super.visit(fragment);
}
@Override
public boolean visit(Assignment assignment) {
if (assignment.getLeftHandSide() instanceof SimpleName sn) {
String varName = sn.getIdentifier();
assignments.computeIfAbsent(varName, k -> new ArrayList<>()).add(assignment.getRightHandSide());
}
return super.visit(assignment);
}
});
}
}
boolean isExternal(Expression expr, java.util.Set<String> visited) {
if (expr == null) return false;
Expression peeled = expr;
while (peeled instanceof ParenthesizedExpression pe) {
peeled = pe.getExpression();
}
while (peeled instanceof CastExpression ce) {
peeled = ce.getExpression();
}
if (peeled instanceof SimpleName sn) {
String varName = sn.getIdentifier();
for (SingleVariableDeclaration svd : parameters) {
if (svd.getName().getIdentifier().equals(varName)) {
return isAnnotatedAsExternal(svd);
}
}
if (assignments.containsKey(varName)) {
if (!visited.add(varName)) {
return false;
}
List<Expression> defs = assignments.get(varName);
for (Expression def : defs) {
if (isExternal(def, visited)) {
return true;
}
}
}
} else if (peeled instanceof MethodInvocation mi) {
if (mi.getExpression() != null && isExternal(mi.getExpression(), visited)) {
return true;
}
for (Object arg : mi.arguments()) {
if (isExternal((Expression) arg, visited)) {
return true;
}
}
}
return false;
}
private boolean isAnnotatedAsExternal(SingleVariableDeclaration svd) {
for (Object modifier : svd.modifiers()) {
if (modifier instanceof Annotation annotation) {
String typeName = annotation.getTypeName().getFullyQualifiedName();
if (typeName.endsWith("PathVariable") || typeName.endsWith("RequestBody") || typeName.endsWith("RequestParam")
|| typeName.endsWith("PathParam") || typeName.endsWith("QueryParam")) {
return true;
}
}
}
return false;
}
}
}

View File

@@ -34,9 +34,12 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
List<String> args = resolveArguments(node.arguments());
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
for (String calledMethod : calledMethods) {
String receiver = getReceiverString(node.getExpression());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args, receiver));
CallEdge edge = new CallEdge(calledMethod, args, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
}
for (Object argObj : node.arguments()) {
if (argObj instanceof ExpressionMethodReference emr) {
@@ -53,7 +56,9 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
implicitArgs.addAll(args);
}
String receiver = getReceiverString(node.getExpression());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, implicitArgs, receiver));
CallEdge edge = new CallEdge(refMethod, implicitArgs, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
}
}
} else {
@@ -73,11 +78,15 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
implicitArgs.addAll(args);
}
String receiver = getReceiverString(node.getExpression());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, implicitArgs, receiver));
CallEdge edge = new CallEdge(calledMethod, implicitArgs, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
List<String> impls = context.getImplementations(fallbackTypeFqn);
for (String impl : impls) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args, receiver));
CallEdge implEdge = new CallEdge(impl + "." + emr.getName().getIdentifier(), args, receiver);
implEdge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(implEdge);
}
}
}
@@ -110,7 +119,10 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
}
List<String> args = resolveArguments(node.arguments());
String receiver = "super";
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args, receiver));
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
CallEdge edge = new CallEdge(calledMethod, args, receiver);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
}
}
}

View File

@@ -38,8 +38,11 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
List<String> args = resolveArguments(node.arguments());
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
for (String calledMethod : calledMethods) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
CallEdge edge = new CallEdge(calledMethod, args);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
}
for (Object argObj : node.arguments()) {
if (argObj instanceof ExpressionMethodReference emr) {
@@ -55,7 +58,9 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
} else {
implicitArgs.addAll(args);
}
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, implicitArgs));
CallEdge edge = new CallEdge(refMethod, implicitArgs);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
}
}
} else {
@@ -74,11 +79,15 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
} else {
implicitArgs.addAll(args);
}
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, implicitArgs));
CallEdge edge = new CallEdge(calledMethod, implicitArgs);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
List<String> impls = context.getImplementations(fallbackTypeFqn);
for (String impl : impls) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args));
CallEdge implEdge = new CallEdge(impl + "." + emr.getName().getIdentifier(), args);
implEdge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(implEdge);
}
}
}
@@ -110,7 +119,10 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
calledMethod = superFqn + "." + methodName;
}
List<String> args = resolveArguments(node.arguments());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
CallEdge edge = new CallEdge(calledMethod, args);
edge.setConstraint(constraint);
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
}
}
}

View File

@@ -95,4 +95,26 @@ public final class AstUtils {
return false;
}
public static String findConditionConstraint(org.eclipse.jdt.core.dom.ASTNode node) {
java.util.List<String> conditions = new java.util.ArrayList<>();
org.eclipse.jdt.core.dom.ASTNode current = node;
while (current != null && !(current instanceof org.eclipse.jdt.core.dom.MethodDeclaration)) {
org.eclipse.jdt.core.dom.ASTNode parent = current.getParent();
if (parent instanceof org.eclipse.jdt.core.dom.IfStatement ifStmt) {
if (ifStmt.getExpression() != current) {
org.eclipse.jdt.core.dom.Expression cond = ifStmt.getExpression();
if (current == ifStmt.getElseStatement()) {
conditions.add("!(" + cond.toString() + ")");
} else {
conditions.add(cond.toString());
}
}
}
current = parent;
}
if (conditions.isEmpty()) return null;
java.util.Collections.reverse(conditions);
return String.join(" && ", conditions);
}
}