This commit is contained in:
2026-06-27 19:18:32 +02:00
parent 5c7cf3639b
commit 76bb88e065
16 changed files with 1199 additions and 100 deletions

View File

@@ -185,9 +185,18 @@ public class ConstructorAnalyzer {
String tdKey = context.getFqn(td);
if (!visitedCtors.add(tdKey)) return;
IMethodBinding resolvedCtor = cic.resolveConstructorBinding();
for (MethodDeclaration ctor : td.getMethods()) {
if (!ctor.isConstructor() || ctor.getBody() == null) continue;
if (ctor.parameters().size() != cic.arguments().size()) continue;
boolean matches = false;
if (resolvedCtor != null && ctor.resolveBinding() != null) {
matches = resolvedCtor.isEqualTo(ctor.resolveBinding()) || resolvedCtor.getKey().equals(ctor.resolveBinding().getKey());
} else {
matches = ctor.parameters().size() == cic.arguments().size();
}
if (!matches) continue;
Map<String, String> paramValues = new HashMap<>();
for (int i = 0; i < ctor.parameters().size(); i++) {
@@ -247,11 +256,22 @@ public class ConstructorAnalyzer {
ctor.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(SuperConstructorInvocation sci) {
IMethodBinding binding = sci.resolveConstructorBinding();
if (binding != null) {
ITypeBinding declaringClass = binding.getDeclaringClass();
if (declaringClass != null) {
TypeDeclaration superTd = context.getTypeDeclaration(declaringClass.getErasure().getQualifiedName());
if (superTd != null) {
processSuperConstructorArgs(superTd, binding, sci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
return super.visit(sci);
}
}
}
String superFqn = context.getSuperclassFqn(td);
if (superFqn != null) {
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
if (superTd != null) {
processSuperConstructorArgs(superTd, sci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
processSuperConstructorArgs(superTd, null, sci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
}
}
return super.visit(sci);
@@ -259,7 +279,8 @@ public class ConstructorAnalyzer {
@Override
public boolean visit(ConstructorInvocation ci) {
processSuperConstructorArgs(td, ci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
IMethodBinding binding = ci.resolveConstructorBinding();
processSuperConstructorArgs(td, binding, ci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
return super.visit(ci);
}
});
@@ -269,6 +290,7 @@ public class ConstructorAnalyzer {
private void processSuperConstructorArgs(
TypeDeclaration superTd,
IMethodBinding resolvedBinding,
List<?> superArgs,
Map<String, String> subClassParamValues,
CodebaseContext context,
@@ -280,7 +302,14 @@ public class ConstructorAnalyzer {
for (MethodDeclaration superCtor : superTd.getMethods()) {
if (!superCtor.isConstructor() || superCtor.getBody() == null) continue;
if (superCtor.parameters().size() != superArgs.size()) continue;
boolean matches = false;
if (resolvedBinding != null && superCtor.resolveBinding() != null) {
matches = resolvedBinding.isEqualTo(superCtor.resolveBinding()) || resolvedBinding.getKey().equals(superCtor.resolveBinding().getKey());
} else {
matches = superCtor.parameters().size() == superArgs.size();
}
if (!matches) continue;
Map<String, String> superParamValues = new HashMap<>();
for (int i = 0; i < superCtor.parameters().size(); i++) {
@@ -343,11 +372,22 @@ public class ConstructorAnalyzer {
superCtor.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(SuperConstructorInvocation sci) {
IMethodBinding binding = sci.resolveConstructorBinding();
if (binding != null) {
ITypeBinding declaringClass = binding.getDeclaringClass();
if (declaringClass != null) {
TypeDeclaration nextSuperTd = context.getTypeDeclaration(declaringClass.getErasure().getQualifiedName());
if (nextSuperTd != null) {
processSuperConstructorArgs(nextSuperTd, binding, sci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
return super.visit(sci);
}
}
}
String superFqn = context.getSuperclassFqn(superTd);
if (superFqn != null) {
TypeDeclaration nextSuperTd = context.getTypeDeclaration(superFqn);
if (nextSuperTd != null) {
processSuperConstructorArgs(nextSuperTd, sci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
processSuperConstructorArgs(nextSuperTd, null, sci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
}
}
return super.visit(sci);
@@ -355,7 +395,8 @@ public class ConstructorAnalyzer {
@Override
public boolean visit(ConstructorInvocation ci) {
processSuperConstructorArgs(superTd, ci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
IMethodBinding binding = ci.resolveConstructorBinding();
processSuperConstructorArgs(superTd, binding, ci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
return super.visit(ci);
}
});

View File

@@ -11,9 +11,12 @@ public class VariableTracer {
private final ConstantResolver constantResolver;
private ConstantExtractor constantExtractor;
private final click.kamil.springstatemachineexporter.ast.common.DataFlowModel dataFlowModel;
public VariableTracer(CodebaseContext context, ConstantResolver constantResolver) {
this.context = context;
this.constantResolver = constantResolver;
this.dataFlowModel = new click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel(context);
}
public void setConstantExtractor(ConstantExtractor constantExtractor) {
@@ -21,101 +24,16 @@ public class VariableTracer {
}
public Expression traceVariable(Expression expr) {
List<Expression> all = traceVariableAll(expr, new HashSet<>());
List<Expression> all = traceVariableAll(expr);
return all.isEmpty() ? expr : all.get(all.size() - 1);
}
public List<Expression> traceVariableAll(Expression expr) {
return traceVariableAll(expr, new HashSet<>());
return dataFlowModel.getReachingDefinitions(expr);
}
public List<Expression> traceVariableAll(Expression expr, Set<String> visitedVariables) {
List<Expression> results = new ArrayList<>();
if (expr instanceof SimpleName sn) {
String varName = sn.getIdentifier();
String methodKey = "";
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
if (enclosingMethod != null) {
TypeDeclaration enclosingType = findEnclosingType(enclosingMethod);
if (enclosingType != null) {
methodKey = context.getFqn(enclosingType) + "." + enclosingMethod.getName().getIdentifier() + "#" + varName;
}
}
if (methodKey.isEmpty()) {
methodKey = varName;
}
if (!visitedVariables.add(methodKey)) {
return results; // Break infinite loop
}
if (enclosingMethod != null) {
enclosingMethod.accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
results.addAll(traceVariableAll(node.getInitializer(), visitedVariables));
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
results.addAll(traceVariableAll(node.getRightHandSide(), visitedVariables));
}
return super.visit(node);
}
});
if (!results.isEmpty()) {
visitedVariables.remove(methodKey);
return results;
}
// Tracing parameter callers (Inter-procedural within the same class)
for (Object paramObj : enclosingMethod.parameters()) {
SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj;
if (param.getName().getIdentifier().equals(varName)) {
int paramIndex = enclosingMethod.parameters().indexOf(param);
TypeDeclaration enclosingType = findEnclosingType(enclosingMethod);
if (enclosingType != null) {
String mName = enclosingMethod.getName().getIdentifier();
List<Expression> callerExprs = new ArrayList<>();
enclosingType.accept(new ASTVisitor() {
@Override
public boolean visit(MethodInvocation node) {
if (node.getName().getIdentifier().equals(mName) && node.arguments().size() > paramIndex) {
callerExprs.addAll(traceVariableAll((Expression) node.arguments().get(paramIndex), visitedVariables));
}
return super.visit(node);
}
@Override
public boolean visit(ExpressionMethodReference node) {
if (node.getName().getIdentifier().equals(mName)) {
if (node.getParent() instanceof MethodInvocation parentMi) {
if (click.kamil.springstatemachineexporter.ast.common.AstUtils.isFunctionalCollectionMethod(parentMi)) {
callerExprs.addAll(traceVariableAll(parentMi.getExpression(), visitedVariables));
} else {
for (Object arg : parentMi.arguments()) {
if (arg != node) {
callerExprs.addAll(traceVariableAll((Expression) arg, visitedVariables));
}
}
}
}
}
return super.visit(node);
}
});
if (!callerExprs.isEmpty()) {
visitedVariables.remove(methodKey);
return callerExprs;
}
}
}
}
}
visitedVariables.remove(methodKey);
}
results.add(expr);
return results;
return dataFlowModel.getReachingDefinitions(expr);
}
public Expression traceLocalSetter(String methodFqn, String varName, String getterName) {

View File

@@ -0,0 +1,101 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.*;
public final class BindingResolver {
private BindingResolver() {
// Prevent instantiation
}
/**
* Resolves the type binding for a given AST Type node.
*/
public static ITypeBinding resolveType(Type type) {
if (type == null) return null;
return type.resolveBinding();
}
/**
* Resolves the type binding for a given AST Expression node.
*/
public static ITypeBinding resolveType(Expression expression) {
if (expression == null) return null;
return expression.resolveTypeBinding();
}
/**
* Resolves the method binding for a MethodInvocation.
*/
public static IMethodBinding resolveMethod(MethodInvocation invocation) {
if (invocation == null) return null;
return invocation.resolveMethodBinding();
}
/**
* Resolves the constructor binding for a ClassInstanceCreation.
*/
public static IMethodBinding resolveConstructor(ClassInstanceCreation creation) {
if (creation == null) return null;
return creation.resolveConstructorBinding();
}
/**
* Resolves the constructor binding for a ConstructorInvocation (e.g. this(...)).
*/
public static IMethodBinding resolveConstructor(ConstructorInvocation invocation) {
if (invocation == null) return null;
return invocation.resolveConstructorBinding();
}
/**
* Resolves the constructor binding for a SuperConstructorInvocation (e.g. super(...)).
*/
public static IMethodBinding resolveConstructor(SuperConstructorInvocation invocation) {
if (invocation == null) return null;
return invocation.resolveConstructorBinding();
}
/**
* Resolves the type binding of a TypeDeclaration.
*/
public static ITypeBinding resolveType(TypeDeclaration td) {
if (td == null) return null;
return td.resolveBinding();
}
/**
* Gets the fully qualified name of a type binding, safely handling nulls and erasure.
*/
public static String getFullyQualifiedName(ITypeBinding binding) {
if (binding == null) return null;
return binding.getErasure().getQualifiedName();
}
/**
* Checks if childBinding is compatible with (extends or implements) targetFqn.
*/
public static boolean isSubtypeOf(ITypeBinding childBinding, String targetFqn) {
if (childBinding == null || targetFqn == null) return false;
// Direct match
if (targetFqn.equals(childBinding.getErasure().getQualifiedName())) {
return true;
}
// Check interfaces
for (ITypeBinding intf : childBinding.getInterfaces()) {
if (isSubtypeOf(intf, targetFqn)) {
return true;
}
}
// Check superclass
ITypeBinding superclass = childBinding.getSuperclass();
if (superclass != null) {
return isSubtypeOf(superclass, targetFqn);
}
return false;
}
}

View File

@@ -0,0 +1,167 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
public class CfgBuilder {
public static ControlFlowGraph build(MethodDeclaration method) {
ControlFlowGraph cfg = new ControlFlowGraph(method);
if (method.getBody() == null) {
cfg.getEntryNode().addSuccessor(cfg.getExitNode());
return cfg;
}
// Build CFG recursively
List<ControlFlowGraph.CfgNode> exits = buildFlow(method.getBody(), List.of(cfg.getEntryNode()), cfg);
for (ControlFlowGraph.CfgNode exit : exits) {
exit.addSuccessor(cfg.getExitNode());
}
return cfg;
}
private static List<ControlFlowGraph.CfgNode> buildFlow(
ASTNode node,
List<ControlFlowGraph.CfgNode> sources,
ControlFlowGraph cfg) {
if (node == null || sources.isEmpty()) {
return sources;
}
if (node instanceof Block block) {
List<ControlFlowGraph.CfgNode> currentSources = sources;
for (Object stmtObj : block.statements()) {
currentSources = buildFlow((ASTNode) stmtObj, currentSources, cfg);
}
return currentSources;
}
if (node instanceof IfStatement ifStmt) {
// Condition node
ControlFlowGraph.CfgNode condNode = new ControlFlowGraph.CfgNode(ifStmt.getExpression(), ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(condNode);
for (ControlFlowGraph.CfgNode src : sources) {
src.addSuccessor(condNode);
}
List<ControlFlowGraph.CfgNode> thenExits = buildFlow(ifStmt.getThenStatement(), List.of(condNode), cfg);
List<ControlFlowGraph.CfgNode> elseExits;
if (ifStmt.getElseStatement() != null) {
elseExits = buildFlow(ifStmt.getElseStatement(), List.of(condNode), cfg);
} else {
elseExits = List.of(condNode);
}
List<ControlFlowGraph.CfgNode> combinedExits = new ArrayList<>();
combinedExits.addAll(thenExits);
combinedExits.addAll(elseExits);
return combinedExits;
}
if (node instanceof WhileStatement whileStmt) {
ControlFlowGraph.CfgNode condNode = new ControlFlowGraph.CfgNode(whileStmt.getExpression(), ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(condNode);
for (ControlFlowGraph.CfgNode src : sources) {
src.addSuccessor(condNode);
}
List<ControlFlowGraph.CfgNode> bodyExits = buildFlow(whileStmt.getBody(), List.of(condNode), cfg);
for (ControlFlowGraph.CfgNode exit : bodyExits) {
exit.addSuccessor(condNode); // Loop back
}
return List.of(condNode); // Exit loop when cond is false
}
if (node instanceof DoStatement doStmt) {
List<ControlFlowGraph.CfgNode> bodyExits = buildFlow(doStmt.getBody(), sources, cfg);
ControlFlowGraph.CfgNode condNode = new ControlFlowGraph.CfgNode(doStmt.getExpression(), ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(condNode);
for (ControlFlowGraph.CfgNode exit : bodyExits) {
exit.addSuccessor(condNode);
}
condNode.addSuccessor(condNode); // Loop back
return List.of(condNode);
}
if (node instanceof ForStatement forStmt) {
// Initializers
List<ControlFlowGraph.CfgNode> currentSources = sources;
for (Object initObj : forStmt.initializers()) {
ControlFlowGraph.CfgNode initNode = new ControlFlowGraph.CfgNode((ASTNode) initObj, ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(initNode);
for (ControlFlowGraph.CfgNode src : currentSources) {
src.addSuccessor(initNode);
}
currentSources = List.of(initNode);
}
// Condition
ControlFlowGraph.CfgNode condNode;
if (forStmt.getExpression() != null) {
condNode = new ControlFlowGraph.CfgNode(forStmt.getExpression(), ControlFlowGraph.CfgNodeType.STATEMENT);
} else {
condNode = new ControlFlowGraph.CfgNode(forStmt, ControlFlowGraph.CfgNodeType.STATEMENT); // Dummy node
}
cfg.addNode(condNode);
for (ControlFlowGraph.CfgNode src : currentSources) {
src.addSuccessor(condNode);
}
List<ControlFlowGraph.CfgNode> bodyExits = buildFlow(forStmt.getBody(), List.of(condNode), cfg);
// Updaters
List<ControlFlowGraph.CfgNode> updaterSources = bodyExits;
for (Object updateObj : forStmt.updaters()) {
ControlFlowGraph.CfgNode updateNode = new ControlFlowGraph.CfgNode((ASTNode) updateObj, ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(updateNode);
for (ControlFlowGraph.CfgNode src : updaterSources) {
src.addSuccessor(updateNode);
}
updaterSources = List.of(updateNode);
}
for (ControlFlowGraph.CfgNode src : updaterSources) {
src.addSuccessor(condNode); // Loop back
}
return List.of(condNode);
}
if (node instanceof EnhancedForStatement effStmt) {
ControlFlowGraph.CfgNode loopNode = new ControlFlowGraph.CfgNode(effStmt.getParameter(), ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(loopNode);
for (ControlFlowGraph.CfgNode src : sources) {
src.addSuccessor(loopNode);
}
List<ControlFlowGraph.CfgNode> bodyExits = buildFlow(effStmt.getBody(), List.of(loopNode), cfg);
for (ControlFlowGraph.CfgNode exit : bodyExits) {
exit.addSuccessor(loopNode); // Loop back
}
return List.of(loopNode);
}
if (node instanceof ReturnStatement returnStmt) {
ControlFlowGraph.CfgNode retNode = new ControlFlowGraph.CfgNode(returnStmt, ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(retNode);
for (ControlFlowGraph.CfgNode src : sources) {
src.addSuccessor(retNode);
}
// Return flows to exit, not to subsequent sequential nodes
retNode.addSuccessor(cfg.getExitNode());
return Collections.emptyList();
}
// Default: general statement
ControlFlowGraph.CfgNode stmtNode = new ControlFlowGraph.CfgNode(node, ControlFlowGraph.CfgNodeType.STATEMENT);
cfg.addNode(stmtNode);
for (ControlFlowGraph.CfgNode src : sources) {
src.addSuccessor(stmtNode);
}
return List.of(stmtNode);
}
}

View File

@@ -356,6 +356,7 @@ public class CodebaseContext {
parser.setSource(source.toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setResolveBindings(resolveBindings);
parser.setBindingsRecovery(true);
Map<String, String> options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_21, options);
@@ -445,6 +446,14 @@ public class CodebaseContext {
}
public String getSuperclassFqn(TypeDeclaration td) {
ITypeBinding binding = td.resolveBinding();
if (binding != null) {
ITypeBinding superBinding = binding.getSuperclass();
if (superBinding != null) {
return superBinding.getErasure().getQualifiedName();
}
}
Type superType = td.getSuperclassType();
if (superType == null) return null;
@@ -466,6 +475,44 @@ public class CodebaseContext {
}
public MethodDeclaration findMethodDeclaration(TypeDeclaration td, String methodName, boolean includeSuper) {
ITypeBinding binding = td.resolveBinding();
if (binding != null && includeSuper) {
IMethodBinding methodBinding = findMethodBinding(binding, methodName);
if (methodBinding != null) {
CompilationUnit declaringCu = classes.get(methodBinding.getDeclaringClass().getErasure().getQualifiedName());
if (declaringCu != null) {
ASTNode declNode = declaringCu.findDeclaringNode(methodBinding.getKey());
if (declNode instanceof MethodDeclaration) {
return (MethodDeclaration) declNode;
}
}
}
}
return findMethodDeclarationLegacy(td, methodName, includeSuper);
}
private IMethodBinding findMethodBinding(ITypeBinding typeBinding, String methodName) {
for (IMethodBinding mb : typeBinding.getDeclaredMethods()) {
if (mb.getName().equals(methodName)) {
return mb;
}
}
ITypeBinding superclass = typeBinding.getSuperclass();
if (superclass != null) {
IMethodBinding mb = findMethodBinding(superclass, methodName);
if (mb != null) return mb;
}
for (ITypeBinding intf : typeBinding.getInterfaces()) {
IMethodBinding mb = findMethodBinding(intf, methodName);
if (mb != null) return mb;
}
return null;
}
private MethodDeclaration findMethodDeclarationLegacy(TypeDeclaration td, String methodName, boolean includeSuper) {
for (MethodDeclaration method : td.getMethods()) {
if (method.getName().getIdentifier().equals(methodName)) {
return method;
@@ -477,7 +524,7 @@ public class CodebaseContext {
if (superFqn != null) {
TypeDeclaration superTd = getTypeDeclaration(superFqn);
if (superTd != null) {
return findMethodDeclaration(superTd, methodName, true);
return findMethodDeclarationLegacy(superTd, methodName, true);
}
}
@@ -487,7 +534,7 @@ public class CodebaseContext {
String interfaceName = extractTypeName(interfaceType);
TypeDeclaration interfaceTd = getTypeDeclaration(interfaceName, (CompilationUnit) td.getRoot());
if (interfaceTd != null) {
MethodDeclaration found = findMethodDeclaration(interfaceTd, methodName, true);
MethodDeclaration found = findMethodDeclarationLegacy(interfaceTd, methodName, true);
if (found != null) return found;
}
}
@@ -665,10 +712,19 @@ public class CodebaseContext {
}
public boolean extendsStateMachineConfigurerAdapter(TypeDeclaration td) {
return extendsStateMachineConfigurerAdapter(td, new HashSet<>());
ITypeBinding binding = td.resolveBinding();
if (binding != null) {
boolean isSubtype = BindingResolver.isSubtypeOf(binding, "org.springframework.statemachine.config.builders.StateMachineConfigurer")
|| BindingResolver.isSubtypeOf(binding, "org.springframework.statemachine.config.StateMachineConfigurerAdapter")
|| BindingResolver.isSubtypeOf(binding, "org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter");
if (isSubtype) {
return true;
}
}
return extendsStateMachineConfigurerAdapterLegacy(td, new HashSet<>());
}
private boolean extendsStateMachineConfigurerAdapter(TypeDeclaration td, Set<String> visited) {
private boolean extendsStateMachineConfigurerAdapterLegacy(TypeDeclaration td, Set<String> visited) {
String fqn = getFqn(td);
if (visited.contains(fqn)) {
return false;
@@ -685,7 +741,7 @@ public class CodebaseContext {
if (knownAdapters.contains(superclassName))
return true;
TypeDeclaration superTd = getTypeDeclaration(superclassName, cu);
if (superTd != null && extendsStateMachineConfigurerAdapter(superTd, visited))
if (superTd != null && extendsStateMachineConfigurerAdapterLegacy(superTd, visited))
return true;
}
@@ -696,7 +752,7 @@ public class CodebaseContext {
if (knownAdapters.contains(interfaceName))
return true;
TypeDeclaration interfaceTd = getTypeDeclaration(interfaceName, cu);
if (interfaceTd != null && extendsStateMachineConfigurerAdapter(interfaceTd, visited))
if (interfaceTd != null && extendsStateMachineConfigurerAdapterLegacy(interfaceTd, visited))
return true;
}
}

View File

@@ -0,0 +1,78 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
public class ControlFlowGraph {
private final MethodDeclaration method;
private final CfgNode entryNode = new CfgNode(null, CfgNodeType.ENTRY);
private final CfgNode exitNode = new CfgNode(null, CfgNodeType.EXIT);
private final List<CfgNode> nodes = new ArrayList<>();
public enum CfgNodeType {
ENTRY,
EXIT,
STATEMENT
}
public static class CfgNode {
private final ASTNode astNode;
private final CfgNodeType type;
private final Set<CfgNode> predecessors = new HashSet<>();
private final Set<CfgNode> successors = new HashSet<>();
public CfgNode(ASTNode astNode, CfgNodeType type) {
this.astNode = astNode;
this.type = type;
}
public ASTNode getAstNode() {
return astNode;
}
public CfgNodeType getType() {
return type;
}
public Set<CfgNode> getPredecessors() {
return predecessors;
}
public Set<CfgNode> getSuccessors() {
return successors;
}
public void addSuccessor(CfgNode succ) {
if (succ != null) {
this.successors.add(succ);
succ.predecessors.add(this);
}
}
}
public ControlFlowGraph(MethodDeclaration method) {
this.method = method;
this.nodes.add(entryNode);
this.nodes.add(exitNode);
}
public MethodDeclaration getMethod() {
return method;
}
public CfgNode getEntryNode() {
return entryNode;
}
public CfgNode getExitNode() {
return exitNode;
}
public List<CfgNode> getNodes() {
return nodes;
}
public void addNode(CfgNode node) {
this.nodes.add(node);
}
}

View File

@@ -0,0 +1,18 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.Expression;
import java.util.List;
public interface DataFlowModel {
/**
* Resolves all possible Expressions that define the value of the given expression
* at its location in the control flow.
*/
List<Expression> getReachingDefinitions(Expression expr);
/**
* Resolves the single constant/literal value if all definitions converge,
* or if a single value is expected.
*/
String resolveValue(Expression expr, CodebaseContext context);
}

View File

@@ -0,0 +1,89 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
public class JdtDataFlowModel implements DataFlowModel {
private final CodebaseContext context;
private final Map<MethodDeclaration, ControlFlowGraph> cfgCache = new HashMap<>();
private final Map<MethodDeclaration, ReachingDefinitions> rdCache = new HashMap<>();
public JdtDataFlowModel(CodebaseContext context) {
this.context = context;
}
private MethodDeclaration findEnclosingMethod(ASTNode node) {
ASTNode current = node;
while (current != null) {
if (current instanceof MethodDeclaration) {
return (MethodDeclaration) current;
}
current = current.getParent();
}
return null;
}
private ReachingDefinitions getReachingDefinitionsForMethod(MethodDeclaration method) {
if (method == null) return null;
return rdCache.computeIfAbsent(method, m -> {
ControlFlowGraph cfg = cfgCache.computeIfAbsent(m, CfgBuilder::build);
return new ReachingDefinitions(cfg);
});
}
@Override
public List<Expression> getReachingDefinitions(Expression expr) {
return getReachingDefinitions(expr, new HashSet<>());
}
private List<Expression> getReachingDefinitions(Expression expr, Set<ASTNode> visited) {
if (expr == null || !visited.add(expr)) return List.of();
if (expr instanceof SimpleName sn) {
MethodDeclaration md = findEnclosingMethod(sn);
ReachingDefinitions rd = getReachingDefinitionsForMethod(md);
if (rd != null) {
Set<ASTNode> defs = rd.getReachingDefinitions(sn, sn.getIdentifier());
if (!defs.isEmpty()) {
List<Expression> exprs = new ArrayList<>();
for (ASTNode def : defs) {
Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
if (defExpr != null) {
exprs.addAll(getReachingDefinitions(defExpr, visited));
} else {
exprs.add(expr);
}
}
visited.remove(expr);
return exprs;
}
}
}
visited.remove(expr);
return List.of(expr);
}
@Override
public String resolveValue(Expression expr, CodebaseContext context) {
if (expr == null) return null;
List<Expression> defs = getReachingDefinitions(expr);
if (defs.isEmpty()) {
return context.resolveExpression(expr);
}
String firstVal = null;
for (Expression def : defs) {
String val = context.resolveExpression(def);
if (val != null) {
if (firstVal == null) {
firstVal = val;
} else if (!firstVal.equals(val)) {
return null;
}
}
}
return firstVal != null ? firstVal : context.resolveExpression(expr);
}
}

View File

@@ -0,0 +1,31 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.Expression;
import click.kamil.springstatemachineexporter.analysis.service.VariableTracer;
import java.util.List;
public class LegacyDataFlowModel implements DataFlowModel {
private final VariableTracer variableTracer;
public LegacyDataFlowModel(VariableTracer variableTracer) {
this.variableTracer = variableTracer;
}
@Override
public List<Expression> getReachingDefinitions(Expression expr) {
if (variableTracer == null || expr == null) {
return expr != null ? List.of(expr) : List.of();
}
return variableTracer.traceVariableAll(expr);
}
@Override
public String resolveValue(Expression expr, CodebaseContext context) {
if (expr == null) return null;
if (variableTracer == null) {
return context.resolveExpression(expr);
}
Expression traced = variableTracer.traceVariable(expr);
return context.resolveExpression(traced);
}
}

View File

@@ -0,0 +1,148 @@
package click.kamil.springstatemachineexporter.ast.common;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
public class ReachingDefinitions {
private final ControlFlowGraph cfg;
private final Map<ControlFlowGraph.CfgNode, Set<ASTNode>> inMap = new HashMap<>();
private final Map<ControlFlowGraph.CfgNode, Set<ASTNode>> outMap = new HashMap<>();
public ReachingDefinitions(ControlFlowGraph cfg) {
this.cfg = cfg;
analyze();
}
private void analyze() {
List<ControlFlowGraph.CfgNode> cfgNodes = cfg.getNodes();
Map<ControlFlowGraph.CfgNode, ASTNode> nodeDefs = new HashMap<>();
Map<String, Set<ASTNode>> varToDefs = new HashMap<>();
for (ControlFlowGraph.CfgNode node : cfgNodes) {
ASTNode ast = node.getAstNode();
if (ast == null) continue;
ASTNode def = findDefinition(ast);
if (def != null) {
nodeDefs.put(node, def);
String varName = getDefinedVariableName(def);
if (varName != null) {
varToDefs.computeIfAbsent(varName, k -> new HashSet<>()).add(def);
}
}
}
for (ControlFlowGraph.CfgNode node : cfgNodes) {
inMap.put(node, new HashSet<>());
outMap.put(node, new HashSet<>());
}
boolean changed = true;
while (changed) {
changed = false;
for (ControlFlowGraph.CfgNode node : cfgNodes) {
Set<ASTNode> newIn = new HashSet<>();
for (ControlFlowGraph.CfgNode pred : node.getPredecessors()) {
newIn.addAll(outMap.get(pred));
}
inMap.put(node, newIn);
Set<ASTNode> newOut = new HashSet<>(newIn);
ASTNode def = nodeDefs.get(node);
if (def != null) {
String varName = getDefinedVariableName(def);
if (varName != null) {
Set<ASTNode> allDefs = varToDefs.get(varName);
if (allDefs != null) {
newOut.removeAll(allDefs);
}
}
newOut.add(def);
}
Set<ASTNode> oldOut = outMap.put(node, newOut);
if (!newOut.equals(oldOut)) {
changed = true;
}
}
}
}
public Set<ASTNode> getReachingDefinitions(ASTNode usageNode, String varName) {
ControlFlowGraph.CfgNode cfgNode = findCfgNodeForUsage(usageNode);
if (cfgNode == null) {
return Collections.emptySet();
}
Set<ASTNode> reaching = new HashSet<>();
Set<ASTNode> inDefs = inMap.get(cfgNode);
if (inDefs != null) {
for (ASTNode def : inDefs) {
if (varName.equals(getDefinedVariableName(def))) {
reaching.add(def);
}
}
}
return reaching;
}
private ControlFlowGraph.CfgNode findCfgNodeForUsage(ASTNode usageNode) {
ASTNode current = usageNode;
while (current != null) {
for (ControlFlowGraph.CfgNode node : cfg.getNodes()) {
if (node.getAstNode() == current) {
return node;
}
}
current = current.getParent();
}
return null;
}
private ASTNode findDefinition(ASTNode ast) {
if (ast instanceof VariableDeclarationStatement vds) {
if (!vds.fragments().isEmpty()) {
return (ASTNode) vds.fragments().get(0);
}
}
if (ast instanceof VariableDeclarationFragment) {
return ast;
}
if (ast instanceof SingleVariableDeclaration) {
return ast;
}
if (ast instanceof Assignment) {
return ast;
}
if (ast instanceof ExpressionStatement es) {
return findDefinition(es.getExpression());
}
return null;
}
public static String getDefinedVariableName(ASTNode def) {
if (def instanceof VariableDeclarationFragment frag) {
return frag.getName().getIdentifier();
}
if (def instanceof SingleVariableDeclaration svd) {
return svd.getName().getIdentifier();
}
if (def instanceof Assignment assign) {
if (assign.getLeftHandSide() instanceof SimpleName sn) {
return sn.getIdentifier();
}
}
return null;
}
public static Expression getDefinitionExpression(ASTNode def) {
if (def instanceof VariableDeclarationFragment frag) {
return frag.getInitializer();
}
if (def instanceof Assignment assign) {
return assign.getRightHandSide();
}
return null;
}
}