stage 1
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user