refactor
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CallGraphPathFinder {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
|
||||||
|
public CallGraphPathFinder(CodebaseContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
||||||
|
if (start.equals(target)) return new ArrayList<>(List.of(start));
|
||||||
|
if (!visited.add(start)) return null; // Path-scoped cycle detection
|
||||||
|
|
||||||
|
List<CallEdge> neighbors = graph.get(start);
|
||||||
|
if (neighbors != null) {
|
||||||
|
for (CallEdge edge : neighbors) {
|
||||||
|
String neighbor = edge.getTargetMethod();
|
||||||
|
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
|
||||||
|
visited.remove(start);
|
||||||
|
return new ArrayList<>(List.of(start, target));
|
||||||
|
}
|
||||||
|
List<String> path = findPath(neighbor, target, graph, visited);
|
||||||
|
if (path != null) {
|
||||||
|
path.add(0, start);
|
||||||
|
visited.remove(start);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Path search dead-end at {} when looking for {}", start, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
visited.remove(start);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||||
|
for (String node : path) {
|
||||||
|
List<CallEdge> edges = callGraph.get(node);
|
||||||
|
if (edges != null) {
|
||||||
|
for (CallEdge edge : edges) {
|
||||||
|
String target = edge.getTargetMethod();
|
||||||
|
if (target != null && (target.contains(".restore") || target.contains(".read"))) {
|
||||||
|
if (edge.getArguments().size() >= 2) {
|
||||||
|
return edge.getArguments().get(1); // The contextObj / machineId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHeuristicMatch(String neighbor, String target) {
|
||||||
|
if (neighbor == null || target == null) return false;
|
||||||
|
if (neighbor.equals(target)) return true;
|
||||||
|
|
||||||
|
if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) {
|
||||||
|
int lastDotNeighbor = neighbor.lastIndexOf('.');
|
||||||
|
int lastDotTarget = target.lastIndexOf('.');
|
||||||
|
if (lastDotNeighbor < 0 || lastDotTarget < 0) return false;
|
||||||
|
|
||||||
|
String methodNeighbor = neighbor.substring(lastDotNeighbor + 1);
|
||||||
|
String methodTarget = target.substring(lastDotTarget + 1);
|
||||||
|
|
||||||
|
if (!methodNeighbor.equals(methodTarget)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String classNeighbor = neighbor.substring(0, lastDotNeighbor);
|
||||||
|
String classTarget = target.substring(0, lastDotTarget);
|
||||||
|
|
||||||
|
if (classNeighbor.equals(classTarget)) return true;
|
||||||
|
|
||||||
|
String simpleClassNeighbor = classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||||
|
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||||
|
|
||||||
|
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||||
|
if (simpleClassNeighbor.contains(simpleClassTarget) || simpleClassTarget.contains(simpleClassNeighbor)) return true;
|
||||||
|
|
||||||
|
List<String> impls = context.getImplementations(classTarget);
|
||||||
|
if (impls != null) {
|
||||||
|
for (String impl : impls) {
|
||||||
|
if (impl.equals(classNeighbor) || impl.endsWith("." + classNeighbor)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> implsN = context.getImplementations(classNeighbor);
|
||||||
|
if (implsN != null) {
|
||||||
|
for (String impl : implsN) {
|
||||||
|
if (impl.equals(classTarget) || impl.endsWith("." + classTarget)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||||
|
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
||||||
|
return simpleNeighbor.equals(simpleTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFullyQualifiedMethod(String methodFqn) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".")) return false;
|
||||||
|
int lastDot = methodFqn.lastIndexOf('.');
|
||||||
|
String classPart = methodFqn.substring(0, lastDot);
|
||||||
|
if (classPart.contains(".")) return true;
|
||||||
|
if (!classPart.isEmpty() && Character.isUpperCase(classPart.charAt(0))) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ConstantExtractor {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
private final ConstantResolver constantResolver;
|
||||||
|
private final Function<MethodInvocation, String> calledMethodResolver;
|
||||||
|
private VariableTracer variableTracer;
|
||||||
|
private ConstructorAnalyzer constructorAnalyzer;
|
||||||
|
|
||||||
|
public ConstantExtractor(CodebaseContext context, ConstantResolver constantResolver, Function<MethodInvocation, String> calledMethodResolver) {
|
||||||
|
this.context = context;
|
||||||
|
this.constantResolver = constantResolver;
|
||||||
|
this.calledMethodResolver = calledMethodResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVariableTracer(VariableTracer variableTracer) {
|
||||||
|
this.variableTracer = variableTracer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConstructorAnalyzer(ConstructorAnalyzer constructorAnalyzer) {
|
||||||
|
this.constructorAnalyzer = constructorAnalyzer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void extractConstantsFromExpression(Expression expr, List<String> constants) {
|
||||||
|
if (expr instanceof QualifiedName qn) {
|
||||||
|
constants.add(qn.toString());
|
||||||
|
} else if (expr instanceof StringLiteral sl) {
|
||||||
|
constants.add(sl.getLiteralValue());
|
||||||
|
} else if (expr instanceof ConditionalExpression ce) {
|
||||||
|
extractConstantsFromExpression(ce.getThenExpression(), constants);
|
||||||
|
extractConstantsFromExpression(ce.getElseExpression(), constants);
|
||||||
|
} else if (expr instanceof ParenthesizedExpression pe) {
|
||||||
|
extractConstantsFromExpression(pe.getExpression(), constants);
|
||||||
|
} else if (expr instanceof CastExpression ce) {
|
||||||
|
extractConstantsFromExpression(ce.getExpression(), constants);
|
||||||
|
} else if (expr instanceof Assignment assignment) {
|
||||||
|
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
|
||||||
|
} else if (expr instanceof SwitchExpression se) {
|
||||||
|
se.accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(YieldStatement ys) {
|
||||||
|
extractConstantsFromExpression(ys.getExpression(), constants);
|
||||||
|
return super.visit(ys);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(ExpressionStatement es) {
|
||||||
|
extractConstantsFromExpression(es.getExpression(), constants);
|
||||||
|
return super.visit(es);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement rs) {
|
||||||
|
extractConstantsFromExpression(rs.getExpression(), constants);
|
||||||
|
return super.visit(rs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (expr instanceof ArrayAccess aa) {
|
||||||
|
extractConstantsFromExpression(aa.getArray(), constants);
|
||||||
|
} else if (expr instanceof ArrayCreation ac && ac.getInitializer() != null) {
|
||||||
|
for (Object expObj : ac.getInitializer().expressions()) {
|
||||||
|
extractConstantsFromArgument((Expression) expObj, constants);
|
||||||
|
}
|
||||||
|
} else if (expr instanceof ArrayInitializer ai) {
|
||||||
|
for (Object expObj : ai.expressions()) {
|
||||||
|
extractConstantsFromArgument((Expression) expObj, constants);
|
||||||
|
}
|
||||||
|
} else if (expr instanceof MethodInvocation mi) {
|
||||||
|
String methodName = mi.getName().getIdentifier();
|
||||||
|
|
||||||
|
if (methodName.equals("get") && mi.arguments().size() == 1 && mi.getExpression() != null) {
|
||||||
|
extractConstantsFromExpression(mi.getExpression(), constants);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((methodName.equals("of") || methodName.equals("asList")) && mi.getExpression() instanceof SimpleName) {
|
||||||
|
for (Object argObj : mi.arguments()) {
|
||||||
|
extractConstantsFromExpression((Expression) argObj, constants);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((methodName.startsWith("get") || methodName.equals("type") || methodName.equals("event")) && mi.getExpression() instanceof SimpleName sn) {
|
||||||
|
String varName = sn.getIdentifier();
|
||||||
|
String propName = methodName.startsWith("get") ? methodName.substring(3) : methodName;
|
||||||
|
|
||||||
|
Block block = findEnclosingBlock(mi);
|
||||||
|
if (block != null) {
|
||||||
|
for (Object stmtObj : block.statements()) {
|
||||||
|
if (stmtObj == mi.getParent() || stmtObj == mi) break;
|
||||||
|
if (stmtObj instanceof ExpressionStatement es) {
|
||||||
|
if (es.getExpression() instanceof MethodInvocation setterMi) {
|
||||||
|
if (setterMi.getName().getIdentifier().equalsIgnoreCase("set" + propName) || setterMi.getName().getIdentifier().equalsIgnoreCase(propName)) {
|
||||||
|
if (setterMi.getExpression() instanceof SimpleName setterSn && setterSn.getIdentifier().equals(varName)) {
|
||||||
|
if (!setterMi.arguments().isEmpty()) {
|
||||||
|
extractConstantsFromExpression((Expression) setterMi.arguments().get(0), constants);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (es.getExpression() instanceof Assignment assignment) {
|
||||||
|
if (assignment.getLeftHandSide() instanceof FieldAccess fa) {
|
||||||
|
if (fa.getExpression() instanceof SimpleName faSn && faSn.getIdentifier().equals(varName)) {
|
||||||
|
if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) {
|
||||||
|
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (assignment.getLeftHandSide() instanceof QualifiedName qqn) {
|
||||||
|
if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) {
|
||||||
|
if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) {
|
||||||
|
extractConstantsFromExpression(assignment.getRightHandSide(), constants);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mi.getExpression() instanceof ClassInstanceCreation cic) {
|
||||||
|
if (cic.getAnonymousClassDeclaration() != null) {
|
||||||
|
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
||||||
|
if (declObj instanceof MethodDeclaration mdecl) {
|
||||||
|
if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) {
|
||||||
|
mdecl.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement rs) {
|
||||||
|
if (rs.getExpression() != null) {
|
||||||
|
extractConstantsFromExpression(rs.getExpression(), constants);
|
||||||
|
}
|
||||||
|
return super.visit(rs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (constructorAnalyzer != null) {
|
||||||
|
boolean handledByLombok = false;
|
||||||
|
if (methodName.startsWith("get") && methodName.length() > 3) {
|
||||||
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
|
TypeDeclaration typeDecl = context.getTypeDeclaration(typeName);
|
||||||
|
if (typeDecl != null) {
|
||||||
|
String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
|
||||||
|
int fieldIndex = constructorAnalyzer.findConstructorArgumentIndexForLombokField(typeDecl, fieldName);
|
||||||
|
if (fieldIndex >= 0 && fieldIndex < cic.arguments().size()) {
|
||||||
|
extractConstantsFromArgument((Expression) cic.arguments().get(fieldIndex), constants);
|
||||||
|
handledByLombok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handledByLombok && methodName != null && !methodName.isEmpty()) {
|
||||||
|
List<String> narrowed = constructorAnalyzer.resolveInlineInstantiationGetterResult(
|
||||||
|
cic, methodName, context, new HashSet<>());
|
||||||
|
if (narrowed.isEmpty()) {
|
||||||
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
|
CompilationUnit contextCu = expr.getRoot() instanceof CompilationUnit ? (CompilationUnit) expr.getRoot() : null;
|
||||||
|
TypeDeclaration typeDecl = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName);
|
||||||
|
if (typeDecl == null) typeDecl = context.getTypeDeclaration(typeName);
|
||||||
|
if (typeDecl != null) {
|
||||||
|
narrowed = resolveMethodReturnConstant(context.getFqn(typeDecl), methodName, 0, new HashSet<>(), contextCu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (narrowed != null && !narrowed.isEmpty()) {
|
||||||
|
constants.addAll(narrowed);
|
||||||
|
handledByLombok = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handledByLombok) {
|
||||||
|
for (Object argObj : cic.arguments()) {
|
||||||
|
extractConstantsFromArgument((Expression) argObj, constants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression currentMi = mi;
|
||||||
|
while (currentMi instanceof MethodInvocation chainMi) {
|
||||||
|
String chainName = chainMi.getName().getIdentifier();
|
||||||
|
if (chainName.equals("setHeader") && chainMi.arguments().size() == 2) {
|
||||||
|
extractConstantsFromExpression((Expression) chainMi.arguments().get(1), constants);
|
||||||
|
} else if ((chainName.equalsIgnoreCase("event") || chainName.equalsIgnoreCase("type") || chainName.equalsIgnoreCase("eventType")) && chainMi.arguments().size() == 1) {
|
||||||
|
extractConstantsFromExpression((Expression) chainMi.arguments().get(0), constants);
|
||||||
|
}
|
||||||
|
currentMi = chainMi.getExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeDeclaration td = findEnclosingType(mi);
|
||||||
|
if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof ThisExpression)) {
|
||||||
|
List<String> values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new HashSet<>(), null);
|
||||||
|
if (values != null) constants.addAll(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void extractConstantsFromArgument(Expression argObj, List<String> constants) {
|
||||||
|
if (argObj instanceof ClassInstanceCreation innerCic) {
|
||||||
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(innerCic.getType());
|
||||||
|
if ("String".equals(typeName)) {
|
||||||
|
extractConstantsFromExpression(argObj, constants);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
extractConstantsFromExpression(argObj, constants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveClassConstantReturns(String className, CodebaseContext context, CompilationUnit contextCu) {
|
||||||
|
if (className != null && className.contains(".")) {
|
||||||
|
String prefix = className.substring(0, className.lastIndexOf('.'));
|
||||||
|
String suffix = className.substring(className.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration prefixTd = contextCu != null ? context.getTypeDeclaration(prefix, contextCu) : context.getTypeDeclaration(prefix);
|
||||||
|
if (prefixTd == null) prefixTd = context.getTypeDeclaration(prefix);
|
||||||
|
boolean isKnownType = prefixTd != null || context.getEnumValues(prefix) != null;
|
||||||
|
if (isKnownType) {
|
||||||
|
String simplePrefix = prefix.contains(".") ? prefix.substring(prefix.lastIndexOf('.') + 1) : prefix;
|
||||||
|
return Collections.singletonList(simplePrefix + "." + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(className, contextCu) : context.getTypeDeclaration(className);
|
||||||
|
if (td == null) td = context.getTypeDeclaration(className);
|
||||||
|
if (td == null) return null;
|
||||||
|
|
||||||
|
final List<String> resolvedConstants = new ArrayList<>();
|
||||||
|
for (MethodDeclaration md : td.getMethods()) {
|
||||||
|
if (md.getBody() != null) {
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
public boolean visit(ReturnStatement node) {
|
||||||
|
if (variableTracer != null) {
|
||||||
|
List<Expression> tracedReturns = variableTracer.traceVariableAll(node.getExpression());
|
||||||
|
for (Expression tracedRet : tracedReturns) {
|
||||||
|
extractConstantsFromExpression(tracedRet, resolvedConstants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!resolvedConstants.isEmpty()) {
|
||||||
|
return resolvedConstants;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
||||||
|
if (depth > 20) return Collections.emptyList();
|
||||||
|
String fqn = className + "." + methodName;
|
||||||
|
if (!visited.add(fqn)) return Collections.emptyList();
|
||||||
|
|
||||||
|
List<String> constants = new ArrayList<>();
|
||||||
|
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
|
||||||
|
if (tempTd == null) {
|
||||||
|
tempTd = context.getTypeDeclaration(className);
|
||||||
|
}
|
||||||
|
final TypeDeclaration td = tempTd;
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && md.getBody() != null) {
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement node) {
|
||||||
|
Expression retExpr = node.getExpression();
|
||||||
|
if (retExpr != null) {
|
||||||
|
boolean handled = false;
|
||||||
|
if (retExpr instanceof MethodInvocation mi && calledMethodResolver != null) {
|
||||||
|
String called = calledMethodResolver.apply(mi);
|
||||||
|
if (called != null && called.contains(".")) {
|
||||||
|
if (visited.contains(called)) {
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
String cName = called.substring(0, called.lastIndexOf('.'));
|
||||||
|
String mName = called.substring(called.lastIndexOf('.') + 1);
|
||||||
|
|
||||||
|
TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
|
||||||
|
if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
|
||||||
|
|
||||||
|
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
|
||||||
|
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu);
|
||||||
|
constants.addAll(delegationResult);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (retExpr instanceof SuperMethodInvocation smi) {
|
||||||
|
String superFqn = context.getSuperclassFqn(td);
|
||||||
|
if (superFqn != null) {
|
||||||
|
String called = superFqn + "." + smi.getName().getIdentifier();
|
||||||
|
if (visited.contains(called)) {
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
String cName = superFqn;
|
||||||
|
String mName = smi.getName().getIdentifier();
|
||||||
|
TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
|
||||||
|
if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
|
||||||
|
|
||||||
|
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
|
||||||
|
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu);
|
||||||
|
constants.addAll(delegationResult);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handled && variableTracer != null && constructorAnalyzer != null) {
|
||||||
|
List<Expression> tracedRetExprs = variableTracer.traceVariableAll(retExpr);
|
||||||
|
for (Expression tracedRetExpr : tracedRetExprs) {
|
||||||
|
extractConstantsFromExpression(tracedRetExpr, constants);
|
||||||
|
String val = constantResolver.resolve(tracedRetExpr, context);
|
||||||
|
if (val != null) {
|
||||||
|
if (val.startsWith("ENUM_SET:")) {
|
||||||
|
for (String eVal : val.substring(9).split(",")) {
|
||||||
|
String parsed = parseEnumSetElement(eVal);
|
||||||
|
if (!constants.contains(parsed)) constants.add(parsed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!constants.contains(val)) constants.add(val);
|
||||||
|
}
|
||||||
|
} else if (tracedRetExpr instanceof SimpleName sn) {
|
||||||
|
List<String> consts = constructorAnalyzer.traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
||||||
|
if (!consts.isEmpty()) {
|
||||||
|
constants.addAll(consts);
|
||||||
|
}
|
||||||
|
} else if (tracedRetExpr instanceof FieldAccess fa) {
|
||||||
|
List<String> consts = constructorAnalyzer.traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited);
|
||||||
|
if (!consts.isEmpty()) {
|
||||||
|
constants.addAll(consts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
List<String> impls = context.getImplementations(className);
|
||||||
|
if (impls != null) {
|
||||||
|
for (String implName : impls) {
|
||||||
|
List<String> delegationResult = resolveMethodReturnConstant(implName, methodName, depth + 1, visited, contextCu);
|
||||||
|
constants.addAll(delegationResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visited.remove(fqn);
|
||||||
|
return constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String parseEnumSetElement(String eVal) {
|
||||||
|
return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block findEnclosingBlock(ASTNode node) {
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
while (parent != null && !(parent instanceof Block)) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return (Block) parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeDeclaration findEnclosingType(ASTNode node) {
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return (TypeDeclaration) parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,649 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ConstructorAnalyzer {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
private final ConstantResolver constantResolver;
|
||||||
|
private VariableTracer variableTracer;
|
||||||
|
private ConstantExtractor constantExtractor;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface RhsResolver {
|
||||||
|
String resolve(Expression rhs, Map<String, String> paramValues, TypeDeclaration td);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConstructorAnalyzer(CodebaseContext context, ConstantResolver constantResolver) {
|
||||||
|
this.context = context;
|
||||||
|
this.constantResolver = constantResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVariableTracer(VariableTracer variableTracer) {
|
||||||
|
this.variableTracer = variableTracer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConstantExtractor(ConstantExtractor constantExtractor) {
|
||||||
|
this.constantExtractor = constantExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
|
||||||
|
for (FieldDeclaration fd : td.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) {
|
||||||
|
Expression right = frag.getInitializer();
|
||||||
|
if (variableTracer != null && constantExtractor != null) {
|
||||||
|
List<Expression> tracedRights = variableTracer.traceVariableAll(right);
|
||||||
|
for (Expression tracedRight : tracedRights) {
|
||||||
|
if (tracedRight instanceof MethodInvocation mi) {
|
||||||
|
String calledName = mi.getName().getIdentifier();
|
||||||
|
String targetClass = context.getFqn(td);
|
||||||
|
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||||
|
if (mi.getExpression() == null) {
|
||||||
|
TypeDeclaration targetTd = context.resolveStaticImport(calledName, cu);
|
||||||
|
if (targetTd != null) {
|
||||||
|
targetClass = context.getFqn(targetTd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.addAll(constantExtractor.resolveMethodReturnConstant(targetClass, calledName, 0, visited, cu));
|
||||||
|
} else {
|
||||||
|
String val = constantResolver.resolve(tracedRight, context);
|
||||||
|
if (val != null) results.add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ASTVisitor assignmentVisitor = new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
Expression left = node.getLeftHandSide();
|
||||||
|
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) ||
|
||||||
|
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) {
|
||||||
|
|
||||||
|
Expression right = node.getRightHandSide();
|
||||||
|
if (variableTracer != null && constantExtractor != null) {
|
||||||
|
List<Expression> tracedRights = variableTracer.traceVariableAll(right);
|
||||||
|
for (Expression tracedRight : tracedRights) {
|
||||||
|
if (tracedRight instanceof MethodInvocation mi) {
|
||||||
|
String calledName = mi.getName().getIdentifier();
|
||||||
|
String targetClass = context.getFqn(td);
|
||||||
|
|
||||||
|
if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) {
|
||||||
|
targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName();
|
||||||
|
} else if (mi.getExpression() instanceof SimpleName) {
|
||||||
|
String exprName = ((SimpleName) mi.getExpression()).getIdentifier();
|
||||||
|
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||||
|
TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu);
|
||||||
|
if (targetTd != null) {
|
||||||
|
targetClass = context.getFqn(targetTd);
|
||||||
|
}
|
||||||
|
} else if (mi.getExpression() == null) {
|
||||||
|
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||||
|
TypeDeclaration targetTd = context.resolveStaticImport(calledName, cu);
|
||||||
|
if (targetTd != null) {
|
||||||
|
targetClass = context.getFqn(targetTd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||||
|
results.addAll(constantExtractor.resolveMethodReturnConstant(targetClass, calledName, 0, visited, cu));
|
||||||
|
} else {
|
||||||
|
String val = constantResolver.resolve(tracedRight, context);
|
||||||
|
if (val != null) results.add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(ConstructorInvocation node) {
|
||||||
|
processConstructorInvocationArgs(node.arguments(), findEnclosingType(node), node, results, fieldName, context, visited);
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(SuperConstructorInvocation node) {
|
||||||
|
TypeDeclaration enclosingTd = findEnclosingType(node);
|
||||||
|
if (enclosingTd != null) {
|
||||||
|
String superFqn = context.getSuperclassFqn(enclosingTd);
|
||||||
|
if (superFqn != null) {
|
||||||
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
|
processConstructorInvocationArgs(node.arguments(), superTd, node, results, fieldName, context, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (MethodDeclaration md : td.getMethods()) {
|
||||||
|
if (md.isConstructor() && md.getBody() != null) {
|
||||||
|
md.getBody().accept(assignmentVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object bodyDecl : td.bodyDeclarations()) {
|
||||||
|
if (bodyDecl instanceof Initializer init && init.getBody() != null) {
|
||||||
|
init.getBody().accept(assignmentVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> buildFieldValuesFromCIC(
|
||||||
|
TypeDeclaration td,
|
||||||
|
ClassInstanceCreation cic,
|
||||||
|
CodebaseContext context,
|
||||||
|
RhsResolver rhsResolver) {
|
||||||
|
|
||||||
|
String actualTypeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
|
TypeDeclaration actualTd = context.getTypeDeclaration(actualTypeName);
|
||||||
|
if (actualTd == null) {
|
||||||
|
actualTd = td;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> fieldValues = new HashMap<>();
|
||||||
|
Set<String> visitedCtors = new HashSet<>();
|
||||||
|
buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver);
|
||||||
|
return fieldValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> buildFieldValuesFromCIC(
|
||||||
|
TypeDeclaration td,
|
||||||
|
ClassInstanceCreation cic,
|
||||||
|
CodebaseContext context) {
|
||||||
|
return buildFieldValuesFromCIC(td, cic, context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildFieldValuesFromCICRecursive(
|
||||||
|
TypeDeclaration td,
|
||||||
|
ClassInstanceCreation cic,
|
||||||
|
CodebaseContext context,
|
||||||
|
Map<String, String> fieldValues,
|
||||||
|
Set<String> visitedCtors,
|
||||||
|
RhsResolver rhsResolver) {
|
||||||
|
|
||||||
|
String tdKey = context.getFqn(td);
|
||||||
|
if (!visitedCtors.add(tdKey)) return;
|
||||||
|
|
||||||
|
for (MethodDeclaration ctor : td.getMethods()) {
|
||||||
|
if (!ctor.isConstructor() || ctor.getBody() == null) continue;
|
||||||
|
if (ctor.parameters().size() != cic.arguments().size()) continue;
|
||||||
|
|
||||||
|
Map<String, String> paramValues = new HashMap<>();
|
||||||
|
for (int i = 0; i < ctor.parameters().size(); i++) {
|
||||||
|
String pName = ((SingleVariableDeclaration) ctor.parameters().get(i)).getName().getIdentifier();
|
||||||
|
Expression argExpr = (Expression) cic.arguments().get(i);
|
||||||
|
List<String> consts = new ArrayList<>();
|
||||||
|
if (constantExtractor != null) {
|
||||||
|
constantExtractor.extractConstantsFromArgument(argExpr, consts);
|
||||||
|
}
|
||||||
|
if (!consts.isEmpty()) {
|
||||||
|
paramValues.put(pName, consts.get(0));
|
||||||
|
} else {
|
||||||
|
String resolved = constantResolver.resolve(argExpr, context);
|
||||||
|
if (resolved != null) paramValues.put(pName, resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (paramValues.isEmpty()) continue;
|
||||||
|
|
||||||
|
ctor.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
Expression lhs = node.getLeftHandSide();
|
||||||
|
Expression rhs = node.getRightHandSide();
|
||||||
|
|
||||||
|
String fieldName = null;
|
||||||
|
if (lhs instanceof FieldAccess fa
|
||||||
|
&& fa.getExpression() instanceof ThisExpression) {
|
||||||
|
fieldName = fa.getName().getIdentifier();
|
||||||
|
} else if (lhs instanceof SimpleName sn
|
||||||
|
&& !paramValues.containsKey(sn.getIdentifier())) {
|
||||||
|
fieldName = sn.getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldName != null) {
|
||||||
|
String paramVal = null;
|
||||||
|
if (rhs instanceof SimpleName snRhs) {
|
||||||
|
paramVal = paramValues.get(snRhs.getIdentifier());
|
||||||
|
} else if (rhsResolver != null) {
|
||||||
|
paramVal = rhsResolver.resolve(rhs, paramValues, td);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramVal != null) {
|
||||||
|
fieldValues.put(fieldName, paramVal);
|
||||||
|
fieldValues.put("this." + fieldName, paramVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctor.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(SuperConstructorInvocation 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(sci);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSuperConstructorArgs(
|
||||||
|
TypeDeclaration superTd,
|
||||||
|
List<?> superArgs,
|
||||||
|
Map<String, String> subClassParamValues,
|
||||||
|
CodebaseContext context,
|
||||||
|
Map<String, String> fieldValues,
|
||||||
|
Set<String> visitedCtors,
|
||||||
|
RhsResolver rhsResolver) {
|
||||||
|
|
||||||
|
if (superTd == null) return;
|
||||||
|
|
||||||
|
for (MethodDeclaration superCtor : superTd.getMethods()) {
|
||||||
|
if (!superCtor.isConstructor() || superCtor.getBody() == null) continue;
|
||||||
|
if (superCtor.parameters().size() != superArgs.size()) continue;
|
||||||
|
|
||||||
|
Map<String, String> superParamValues = new HashMap<>();
|
||||||
|
for (int i = 0; i < superCtor.parameters().size(); i++) {
|
||||||
|
String pName = ((SingleVariableDeclaration) superCtor.parameters().get(i)).getName().getIdentifier();
|
||||||
|
Expression argExpr = (Expression) superArgs.get(i);
|
||||||
|
|
||||||
|
String resolved = null;
|
||||||
|
if (argExpr instanceof SimpleName sn) {
|
||||||
|
resolved = subClassParamValues.get(sn.getIdentifier());
|
||||||
|
}
|
||||||
|
if (resolved == null) {
|
||||||
|
List<String> consts = new ArrayList<>();
|
||||||
|
if (constantExtractor != null) {
|
||||||
|
constantExtractor.extractConstantsFromArgument(argExpr, consts);
|
||||||
|
}
|
||||||
|
if (!consts.isEmpty()) {
|
||||||
|
resolved = consts.get(0);
|
||||||
|
} else {
|
||||||
|
resolved = constantResolver.resolve(argExpr, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resolved != null) {
|
||||||
|
superParamValues.put(pName, resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (superParamValues.isEmpty()) continue;
|
||||||
|
|
||||||
|
superCtor.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
Expression lhs = node.getLeftHandSide();
|
||||||
|
Expression rhs = node.getRightHandSide();
|
||||||
|
|
||||||
|
String fieldName = null;
|
||||||
|
if (lhs instanceof FieldAccess fa
|
||||||
|
&& fa.getExpression() instanceof ThisExpression) {
|
||||||
|
fieldName = fa.getName().getIdentifier();
|
||||||
|
} else if (lhs instanceof SimpleName sn
|
||||||
|
&& !superParamValues.containsKey(sn.getIdentifier())) {
|
||||||
|
fieldName = sn.getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldName != null) {
|
||||||
|
String paramVal = null;
|
||||||
|
if (rhs instanceof SimpleName snRhs) {
|
||||||
|
paramVal = superParamValues.get(snRhs.getIdentifier());
|
||||||
|
} else if (rhsResolver != null) {
|
||||||
|
paramVal = rhsResolver.resolve(rhs, superParamValues, superTd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramVal != null) {
|
||||||
|
fieldValues.put(fieldName, paramVal);
|
||||||
|
fieldValues.put("this." + fieldName, paramVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
superCtor.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(SuperConstructorInvocation 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(sci);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String evaluateMethodCallInConstructor(
|
||||||
|
Expression rhs,
|
||||||
|
Map<String, String> paramValues,
|
||||||
|
TypeDeclaration td) {
|
||||||
|
|
||||||
|
if (!(rhs instanceof MethodInvocation mi)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String methodName = mi.getName().getIdentifier();
|
||||||
|
Expression receiver = mi.getExpression();
|
||||||
|
if (receiver != null && !(receiver instanceof ThisExpression)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodDeclaration method = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (method == null || method.getBody() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> localVars = new HashMap<>(paramValues);
|
||||||
|
return constantResolver.evaluateMethodBodyWithLocals(method, localVars, context, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassInstanceCreation findReturnedCIC(
|
||||||
|
String className, String methodName, CodebaseContext context) {
|
||||||
|
return findReturnedCIC(className, methodName, context, new HashSet<>(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassInstanceCreation findReturnedCIC(
|
||||||
|
String className, String methodName, CodebaseContext context,
|
||||||
|
Set<String> visited, int depth) {
|
||||||
|
if (depth > 50 || !visited.add(className + "#" + methodName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && md.getBody() != null) {
|
||||||
|
ClassInstanceCreation[] result = {null};
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement rs) {
|
||||||
|
if (rs.getExpression() instanceof ClassInstanceCreation cic) {
|
||||||
|
result[0] = cic;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (result[0] != null) return result[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> impls = context.getImplementations(className);
|
||||||
|
for (String impl : impls) {
|
||||||
|
ClassInstanceCreation cic = findReturnedCIC(impl, methodName, context, visited, depth + 1);
|
||||||
|
if (cic != null) return cic;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> resolveInlineInstantiationGetterResult(
|
||||||
|
ClassInstanceCreation cic,
|
||||||
|
String getterName,
|
||||||
|
CodebaseContext context,
|
||||||
|
Set<String> visited) {
|
||||||
|
|
||||||
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
|
CompilationUnit contextCu = cic.getRoot() instanceof CompilationUnit ? (CompilationUnit) cic.getRoot() : null;
|
||||||
|
TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName);
|
||||||
|
if (td == null) td = context.getTypeDeclaration(typeName);
|
||||||
|
if (td == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true);
|
||||||
|
if (getter == null || getter.getBody() == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
String getterKey = typeName + "#" + getterName;
|
||||||
|
if (!visited.add(getterKey)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> fieldValues = buildFieldValuesFromCIC(td, cic, context);
|
||||||
|
if (fieldValues.isEmpty()) return Collections.emptyList();
|
||||||
|
|
||||||
|
String result = constantResolver.evaluateMethodBodyWithLocals(getter, fieldValues, context, visited);
|
||||||
|
return result != null ? List.of(result) : Collections.emptyList();
|
||||||
|
} finally {
|
||||||
|
visited.remove(getterKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int findConstructorArgumentIndexForLombokField(TypeDeclaration td, String fieldName) {
|
||||||
|
boolean isAllArgsConstructor = false;
|
||||||
|
boolean isRequiredArgsConstructor = false;
|
||||||
|
boolean isData = false;
|
||||||
|
boolean isValue = false;
|
||||||
|
|
||||||
|
for (Object modObj : td.modifiers()) {
|
||||||
|
if (modObj instanceof MarkerAnnotation ma) {
|
||||||
|
String name = ma.getTypeName().getFullyQualifiedName();
|
||||||
|
if (name.equals("AllArgsConstructor") || name.equals("lombok.AllArgsConstructor")) isAllArgsConstructor = true;
|
||||||
|
if (name.equals("RequiredArgsConstructor") || name.equals("lombok.RequiredArgsConstructor")) isRequiredArgsConstructor = true;
|
||||||
|
if (name.equals("Data") || name.equals("lombok.Data")) isData = true;
|
||||||
|
if (name.equals("Value") || name.equals("lombok.Value")) isValue = true;
|
||||||
|
} else if (modObj instanceof NormalAnnotation na) {
|
||||||
|
String name = na.getTypeName().getFullyQualifiedName();
|
||||||
|
if (name.equals("AllArgsConstructor") || name.equals("lombok.AllArgsConstructor")) isAllArgsConstructor = true;
|
||||||
|
if (name.equals("RequiredArgsConstructor") || name.equals("lombok.RequiredArgsConstructor")) isRequiredArgsConstructor = true;
|
||||||
|
if (name.equals("Data") || name.equals("lombok.Data")) isData = true;
|
||||||
|
if (name.equals("Value") || name.equals("lombok.Value")) isValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAllArgsConstructor && !isRequiredArgsConstructor && !isData && !isValue) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (FieldDeclaration fd : td.getFields()) {
|
||||||
|
boolean isStatic = false;
|
||||||
|
boolean isFinal = false;
|
||||||
|
boolean hasNonNull = false;
|
||||||
|
for (Object modObj : fd.modifiers()) {
|
||||||
|
if (modObj instanceof Modifier mod) {
|
||||||
|
if (mod.isStatic()) isStatic = true;
|
||||||
|
if (mod.isFinal()) isFinal = true;
|
||||||
|
} else if (modObj instanceof MarkerAnnotation ma) {
|
||||||
|
if (ma.getTypeName().getFullyQualifiedName().endsWith("NonNull")) hasNonNull = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isStatic) continue;
|
||||||
|
|
||||||
|
if (isRequiredArgsConstructor && !isAllArgsConstructor && !isFinal && !hasNonNull) continue;
|
||||||
|
if (isData && !isAllArgsConstructor && !isFinal && !hasNonNull) continue;
|
||||||
|
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(fieldName)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processConstructorInvocationArgs(List<?> arguments, TypeDeclaration targetTd, ASTNode callNode, List<String> results, String fieldName, CodebaseContext context, Set<String> visited) {
|
||||||
|
if (targetTd == null) return;
|
||||||
|
MethodDeclaration callerMd = findEnclosingMethod(callNode);
|
||||||
|
|
||||||
|
IMethodBinding resolvedBinding = null;
|
||||||
|
if (callNode instanceof ConstructorInvocation ci) {
|
||||||
|
resolvedBinding = ci.resolveConstructorBinding();
|
||||||
|
} else if (callNode instanceof SuperConstructorInvocation sci) {
|
||||||
|
resolvedBinding = sci.resolveConstructorBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MethodDeclaration otherMd : targetTd.getMethods()) {
|
||||||
|
boolean matches = false;
|
||||||
|
if (resolvedBinding != null && otherMd.resolveBinding() != null) {
|
||||||
|
matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey());
|
||||||
|
} else {
|
||||||
|
matches = otherMd.isConstructor() && otherMd.parameters().size() == arguments.size() && otherMd != callerMd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, new HashSet<>());
|
||||||
|
if (targetIdx >= 0 && targetIdx < arguments.size()) {
|
||||||
|
Expression arg = (Expression) arguments.get(targetIdx);
|
||||||
|
String val = constantResolver.resolve(arg, context);
|
||||||
|
if (val != null) {
|
||||||
|
if (val.startsWith("ENUM_SET:")) {
|
||||||
|
for (String eVal : val.substring(9).split(",")) {
|
||||||
|
if (constantExtractor != null) {
|
||||||
|
results.add(constantExtractor.parseEnumSetElement(eVal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.add(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findAssignedParameterIndex(MethodDeclaration constructorMd, String fieldName, CodebaseContext context, Set<MethodDeclaration> visited) {
|
||||||
|
if (constructorMd == null || constructorMd.getBody() == null) return -1;
|
||||||
|
if (!visited.add(constructorMd)) return -1;
|
||||||
|
|
||||||
|
final int[] foundIdx = {-1};
|
||||||
|
constructorMd.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment asn) {
|
||||||
|
Expression left = asn.getLeftHandSide();
|
||||||
|
if ((left instanceof SimpleName sn && sn.getIdentifier().equals(fieldName)) ||
|
||||||
|
(left instanceof FieldAccess fa && fa.getName().getIdentifier().equals(fieldName)) ||
|
||||||
|
(left instanceof SuperFieldAccess sfa && sfa.getName().getIdentifier().equals(fieldName))) {
|
||||||
|
Expression right = asn.getRightHandSide();
|
||||||
|
String rightName = extractVariableName(right);
|
||||||
|
if (rightName != null) {
|
||||||
|
for (int i = 0; i < constructorMd.parameters().size(); i++) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i);
|
||||||
|
if (svd.getName().getIdentifier().equals(rightName)) {
|
||||||
|
foundIdx[0] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(asn);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(ConstructorInvocation node) {
|
||||||
|
IMethodBinding resolvedBinding = node.resolveConstructorBinding();
|
||||||
|
TypeDeclaration enclosingTd = findEnclosingType(node);
|
||||||
|
if (enclosingTd != null) {
|
||||||
|
for (MethodDeclaration otherMd : enclosingTd.getMethods()) {
|
||||||
|
boolean matches = false;
|
||||||
|
if (resolvedBinding != null && otherMd.resolveBinding() != null) {
|
||||||
|
matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey());
|
||||||
|
} else {
|
||||||
|
matches = otherMd.isConstructor() && otherMd != constructorMd && otherMd.parameters().size() == node.arguments().size();
|
||||||
|
}
|
||||||
|
if (matches) {
|
||||||
|
int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited);
|
||||||
|
if (targetIdx >= 0 && targetIdx < node.arguments().size()) {
|
||||||
|
Expression arg = (Expression) node.arguments().get(targetIdx);
|
||||||
|
String argName = extractVariableName(arg);
|
||||||
|
if (argName != null) {
|
||||||
|
for (int i = 0; i < constructorMd.parameters().size(); i++) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i);
|
||||||
|
if (svd.getName().getIdentifier().equals(argName)) {
|
||||||
|
foundIdx[0] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(SuperConstructorInvocation node) {
|
||||||
|
IMethodBinding resolvedBinding = node.resolveConstructorBinding();
|
||||||
|
TypeDeclaration enclosingTd = findEnclosingType(node);
|
||||||
|
if (enclosingTd != null) {
|
||||||
|
String superFqn = context.getSuperclassFqn(enclosingTd);
|
||||||
|
if (superFqn != null) {
|
||||||
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
|
if (superTd != null) {
|
||||||
|
for (MethodDeclaration otherMd : superTd.getMethods()) {
|
||||||
|
boolean matches = false;
|
||||||
|
if (resolvedBinding != null && otherMd.resolveBinding() != null) {
|
||||||
|
matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey());
|
||||||
|
} else {
|
||||||
|
matches = otherMd.isConstructor() && otherMd.parameters().size() == node.arguments().size();
|
||||||
|
}
|
||||||
|
if (matches) {
|
||||||
|
int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited);
|
||||||
|
if (targetIdx >= 0 && targetIdx < node.arguments().size()) {
|
||||||
|
Expression arg = (Expression) node.arguments().get(targetIdx);
|
||||||
|
String argName = extractVariableName(arg);
|
||||||
|
if (argName != null) {
|
||||||
|
for (int i = 0; i < constructorMd.parameters().size(); i++) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i);
|
||||||
|
if (svd.getName().getIdentifier().equals(argName)) {
|
||||||
|
foundIdx[0] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return foundIdx[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractVariableName(Expression expr) {
|
||||||
|
if (expr instanceof SimpleName sn) return sn.getIdentifier();
|
||||||
|
if (expr instanceof FieldAccess fa) return fa.getName().getIdentifier();
|
||||||
|
if (expr instanceof SuperFieldAccess sfa) return sfa.getName().getIdentifier();
|
||||||
|
if (expr instanceof CastExpression ce) return extractVariableName(ce.getExpression());
|
||||||
|
if (expr instanceof ParenthesizedExpression pe) return extractVariableName(pe.getExpression());
|
||||||
|
if (expr instanceof MethodInvocation mi && (mi.getName().getIdentifier().equals("requireNonNull") || mi.getName().getIdentifier().equals("notNull"))) {
|
||||||
|
if (!mi.arguments().isEmpty()) return extractVariableName((Expression)mi.arguments().get(0));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return (MethodDeclaration) parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeDeclaration findEnclosingType(ASTNode node) {
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return (TypeDeclaration) parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class TypeResolver {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
|
||||||
|
public TypeResolver(CodebaseContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParameterIndex(String methodFqn, String paramName) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".")) return -1;
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null) {
|
||||||
|
for (int i = 0; i < md.parameters().size(); i++) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i);
|
||||||
|
if (svd.getName().getIdentifier().equals(paramName)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParameterType(String methodFqn, int index) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && index < md.parameters().size()) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(index);
|
||||||
|
return svd.getType().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTypeCompatible(String actualType, String expectedType) {
|
||||||
|
if (actualType == null || expectedType == null) return true;
|
||||||
|
if (expectedType.equals("Object") || expectedType.equals("java.lang.Object")) return true;
|
||||||
|
if (actualType.equals("Object") || actualType.equals("java.lang.Object")) return true;
|
||||||
|
if (expectedType.length() == 1 && Character.isUpperCase(expectedType.charAt(0))) return true;
|
||||||
|
if (actualType.length() == 1 && Character.isUpperCase(actualType.charAt(0))) return true;
|
||||||
|
|
||||||
|
String originalExpectedType = expectedType;
|
||||||
|
|
||||||
|
if (actualType.contains("<")) {
|
||||||
|
String rawType = actualType.substring(0, actualType.indexOf('<'));
|
||||||
|
if (rawType.equals("List") || rawType.equals("Set") || rawType.equals("Collection") || rawType.equals("Iterable") || rawType.endsWith("List") || rawType.endsWith("Set") || rawType.endsWith("Collection")) {
|
||||||
|
int start = actualType.indexOf('<');
|
||||||
|
int end = actualType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
actualType = actualType.substring(start + 1, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expectedType.contains("<")) {
|
||||||
|
String rawType = expectedType.substring(0, expectedType.indexOf('<'));
|
||||||
|
if (rawType.equals("List") || rawType.equals("Set") || rawType.equals("Collection") || rawType.equals("Iterable") || rawType.endsWith("List") || rawType.endsWith("Set") || rawType.endsWith("Collection")) {
|
||||||
|
int start = expectedType.indexOf('<');
|
||||||
|
int end = expectedType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
expectedType = expectedType.substring(start + 1, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actualType.contains("<")) {
|
||||||
|
actualType = actualType.substring(0, actualType.indexOf('<'));
|
||||||
|
}
|
||||||
|
if (expectedType.contains("<")) {
|
||||||
|
expectedType = expectedType.substring(0, expectedType.indexOf('<'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actualType.equals(expectedType)) return true;
|
||||||
|
if (actualType.endsWith("." + expectedType) || expectedType.endsWith("." + actualType)) return true;
|
||||||
|
|
||||||
|
AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(actualType);
|
||||||
|
if (td != null) {
|
||||||
|
if (td instanceof TypeDeclaration typeDecl) {
|
||||||
|
String superFqn = context.getSuperclassFqn(typeDecl);
|
||||||
|
while (superFqn != null) {
|
||||||
|
if (superFqn.contains("<")) {
|
||||||
|
superFqn = superFqn.substring(0, superFqn.indexOf('<'));
|
||||||
|
}
|
||||||
|
if (superFqn.equals(expectedType) || superFqn.endsWith("." + expectedType) || expectedType.endsWith("." + superFqn)) return true;
|
||||||
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
|
superFqn = superTd != null ? context.getSuperclassFqn(superTd) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List interfaces = java.util.Collections.emptyList();
|
||||||
|
if (td instanceof TypeDeclaration typeDecl) {
|
||||||
|
interfaces = typeDecl.superInterfaceTypes();
|
||||||
|
} else if (td instanceof EnumDeclaration enumDecl) {
|
||||||
|
interfaces = enumDecl.superInterfaceTypes();
|
||||||
|
} else if (td instanceof RecordDeclaration recordDecl) {
|
||||||
|
interfaces = recordDecl.superInterfaceTypes();
|
||||||
|
}
|
||||||
|
for (Object interfaceType : interfaces) {
|
||||||
|
String itStr = interfaceType.toString();
|
||||||
|
if (itStr.contains("<")) {
|
||||||
|
itStr = itStr.substring(0, itStr.indexOf('<'));
|
||||||
|
}
|
||||||
|
if (itStr.equals(expectedType) || itStr.endsWith("." + expectedType) || expectedType.endsWith("." + itStr)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalExpectedType.contains("<")) {
|
||||||
|
int start = originalExpectedType.indexOf('<');
|
||||||
|
int end = originalExpectedType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
String sub = originalExpectedType.substring(start + 1, end);
|
||||||
|
for (String part : sub.split(",")) {
|
||||||
|
String typeArg = part.trim();
|
||||||
|
if (isTypeCompatible(actualType, typeArg)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,643 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class VariableTracer {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
private final ConstantResolver constantResolver;
|
||||||
|
private ConstantExtractor constantExtractor;
|
||||||
|
|
||||||
|
public VariableTracer(CodebaseContext context, ConstantResolver constantResolver) {
|
||||||
|
this.context = context;
|
||||||
|
this.constantResolver = constantResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConstantExtractor(ConstantExtractor constantExtractor) {
|
||||||
|
this.constantExtractor = constantExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expression traceVariable(Expression expr) {
|
||||||
|
List<Expression> all = traceVariableAll(expr, new HashSet<>());
|
||||||
|
return all.isEmpty() ? expr : all.get(all.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Expression> traceVariableAll(Expression expr) {
|
||||||
|
return traceVariableAll(expr, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expression traceLocalSetter(String methodFqn, String varName, String getterName) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && md.getBody() != null) {
|
||||||
|
final Expression[] setterArg = new Expression[1];
|
||||||
|
String propName = getterName.startsWith("get") ? getterName.substring(3) : getterName;
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(MethodInvocation node) {
|
||||||
|
if (node.getName().getIdentifier().equalsIgnoreCase("set" + propName) || node.getName().getIdentifier().equalsIgnoreCase(propName)) {
|
||||||
|
if (node.getExpression() instanceof SimpleName sn && sn.getIdentifier().equals(varName)) {
|
||||||
|
if (!node.arguments().isEmpty()) {
|
||||||
|
setterArg[0] = (Expression) node.arguments().get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
if (node.getLeftHandSide() instanceof FieldAccess fa) {
|
||||||
|
if (fa.getExpression() instanceof SimpleName faSn && faSn.getIdentifier().equals(varName)) {
|
||||||
|
if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) {
|
||||||
|
setterArg[0] = node.getRightHandSide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (node.getLeftHandSide() instanceof QualifiedName qqn) {
|
||||||
|
if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) {
|
||||||
|
if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) {
|
||||||
|
setterArg[0] = node.getRightHandSide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return setterArg[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
|
||||||
|
if (methodFqn == null) return null;
|
||||||
|
int lastDot = methodFqn.lastIndexOf('.');
|
||||||
|
if (lastDot > 0) {
|
||||||
|
String fqn = methodFqn.substring(0, lastDot);
|
||||||
|
String methodName = methodFqn.substring(lastDot + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(fqn);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, false);
|
||||||
|
if (md != null) {
|
||||||
|
for (Object pObj : md.parameters()) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
||||||
|
if (svd.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
return svd.getType().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String[] foundType = new String[1];
|
||||||
|
if (md.getBody() != null) {
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationStatement node) {
|
||||||
|
for (Object fragObj : node.fragments()) {
|
||||||
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
foundType[0] = node.getType().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(LambdaExpression node) {
|
||||||
|
for (Object paramObj : node.parameters()) {
|
||||||
|
VariableDeclaration param = (VariableDeclaration) paramObj;
|
||||||
|
if (param.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
if (param instanceof SingleVariableDeclaration svd && svd.getType() != null) {
|
||||||
|
foundType[0] = svd.getType().toString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
if (parent instanceof MethodInvocation mi) {
|
||||||
|
Expression caller = mi.getExpression();
|
||||||
|
|
||||||
|
boolean hasMap = false;
|
||||||
|
while (caller instanceof MethodInvocation chainMi) {
|
||||||
|
String mName = chainMi.getName().getIdentifier();
|
||||||
|
if (mName.equals("map") || mName.equals("flatMap")) {
|
||||||
|
hasMap = true;
|
||||||
|
if (chainMi.arguments().size() == 1 && chainMi.arguments().get(0) instanceof LambdaExpression lambda) {
|
||||||
|
if (lambda.getBody() instanceof MethodInvocation mapMi) {
|
||||||
|
Expression mapCaller = mapMi.getExpression();
|
||||||
|
if (mapCaller instanceof SimpleName mapSn) {
|
||||||
|
String mapCallerType = getVariableDeclaredType(methodFqn, mapSn.getIdentifier());
|
||||||
|
if (mapCallerType != null) {
|
||||||
|
TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.')));
|
||||||
|
CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(mapCallerType, cu);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration mapMd = context.findMethodDeclaration(td, mapMi.getName().getIdentifier(), true);
|
||||||
|
if (mapMd != null && mapMd.getReturnType2() != null) {
|
||||||
|
foundType[0] = mapMd.getReturnType2().toString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mName.equals("stream") || mName.equals("filter") || mName.equals("map") ||
|
||||||
|
mName.equals("flatMap") || mName.equals("peek") || mName.equals("distinct") ||
|
||||||
|
mName.equals("sorted") || mName.equals("limit") || mName.equals("skip")) {
|
||||||
|
caller = chainMi.getExpression();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMap) {
|
||||||
|
if (caller instanceof SimpleName callerSn) {
|
||||||
|
String callerName = callerSn.getIdentifier();
|
||||||
|
if (!callerName.equals(cleanFieldName)) {
|
||||||
|
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
||||||
|
if (callerType != null && callerType.contains("<")) {
|
||||||
|
int start = callerType.indexOf('<');
|
||||||
|
int end = callerType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
foundType[0] = callerType.substring(start + 1, end);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (caller instanceof FieldAccess fa) {
|
||||||
|
String callerName = fa.getName().getIdentifier();
|
||||||
|
if (!callerName.equals(cleanFieldName)) {
|
||||||
|
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
||||||
|
if (callerType != null && callerType.contains("<")) {
|
||||||
|
int start = callerType.indexOf('<');
|
||||||
|
int end = callerType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
foundType[0] = callerType.substring(start + 1, end);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (foundType[0] != null) return foundType[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to fields
|
||||||
|
for (FieldDeclaration fd : td.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
return fd.getType().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final fallback: ask constantExtractor to resolve it via method return constant
|
||||||
|
if (constantExtractor != null && methodFqn.contains(".")) {
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
CompilationUnit cu = td != null && td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||||
|
List<String> values = constantExtractor.resolveMethodReturnConstant(cleanFieldName, methodName, 0, new HashSet<>(), cu);
|
||||||
|
if (values != null && !values.isEmpty()) {
|
||||||
|
// If it resolves to some class constant, we could infer type or return a fallback
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String traceLocalVariable(String methodFqn, String varName) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && md.getBody() != null) {
|
||||||
|
List<Expression> initializers = new ArrayList<>();
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationFragment node) {
|
||||||
|
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
||||||
|
initializers.add(node.getInitializer());
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
||||||
|
initializers.add(node.getRightHandSide());
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!initializers.isEmpty()) {
|
||||||
|
List<String> stringified = new ArrayList<>();
|
||||||
|
for (Expression expr : initializers) {
|
||||||
|
Expression traced = traceVariable(expr);
|
||||||
|
if (traced instanceof MethodInvocation mi) {
|
||||||
|
Expression innerMost = unwrapMethodInvocation(mi, 0);
|
||||||
|
if (innerMost instanceof MethodInvocation innerMi) {
|
||||||
|
if (innerMi.getExpression() instanceof SimpleName sn) {
|
||||||
|
stringified.add(sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()");
|
||||||
|
} else {
|
||||||
|
stringified.add(innerMi.getName().getIdentifier() + "()");
|
||||||
|
}
|
||||||
|
} else if (innerMost instanceof SimpleName sn) {
|
||||||
|
stringified.add(sn.getIdentifier());
|
||||||
|
} else {
|
||||||
|
stringified.add(innerMost.toString());
|
||||||
|
}
|
||||||
|
} else if (traced instanceof SimpleName sn) {
|
||||||
|
stringified.add(sn.getIdentifier());
|
||||||
|
} else if (traced instanceof ArrayInitializer) {
|
||||||
|
stringified.add("new Object[]" + traced.toString());
|
||||||
|
} else {
|
||||||
|
stringified.add(traced.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stringified.size() == 1) return stringified.get(0);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < stringified.size() - 1; i++) {
|
||||||
|
sb.append("true ? ").append(stringified.get(i)).append(" : ");
|
||||||
|
}
|
||||||
|
sb.append(stringified.get(stringified.size() - 1));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If local variable not found, check field assignments
|
||||||
|
String cleanFieldName = varName.startsWith("this.") ? varName.substring(5) : varName;
|
||||||
|
final Expression[] fieldInitializer = new Expression[1];
|
||||||
|
ASTVisitor fieldVisitor = new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationFragment node) {
|
||||||
|
if (node.getName().getIdentifier().equals(cleanFieldName) && node.getInitializer() != null) {
|
||||||
|
fieldInitializer[0] = node.getInitializer();
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
Expression left = node.getLeftHandSide();
|
||||||
|
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(cleanFieldName)) ||
|
||||||
|
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(cleanFieldName))) {
|
||||||
|
fieldInitializer[0] = node.getRightHandSide();
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (FieldDeclaration fd : td.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(cleanFieldName) && frag.getInitializer() != null) {
|
||||||
|
fieldInitializer[0] = frag.getInitializer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fieldInitializer[0] != null) return fieldInitializer[0].toString();
|
||||||
|
|
||||||
|
for (MethodDeclaration classMd : td.getMethods()) {
|
||||||
|
if (classMd.isConstructor() && classMd.getBody() != null) {
|
||||||
|
classMd.getBody().accept(fieldVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fieldInitializer[0] != null) return fieldInitializer[0].toString();
|
||||||
|
|
||||||
|
for (MethodDeclaration classMd : td.getMethods()) {
|
||||||
|
if (!classMd.isConstructor() && classMd.getBody() != null) {
|
||||||
|
classMd.getBody().accept(fieldVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fieldInitializer[0] != null) return fieldInitializer[0].toString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String traceLocalVariable(String methodFqn, String varName, Map<String, String> parameterValues) {
|
||||||
|
String result = traceLocalVariable(methodFqn, varName);
|
||||||
|
if (result != null && parameterValues != null && !parameterValues.isEmpty()) {
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.')));
|
||||||
|
if (td != null) {
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && md.getBody() != null) {
|
||||||
|
final ASTNode[] switchNode = new ASTNode[1];
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationFragment node) {
|
||||||
|
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
||||||
|
Expression init = node.getInitializer();
|
||||||
|
if (init.getNodeType() == ASTNode.SWITCH_EXPRESSION) {
|
||||||
|
switchNode[0] = init;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (init.getNodeType() == ASTNode.SWITCH_STATEMENT) {
|
||||||
|
switchNode[0] = init;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
||||||
|
Expression rhs = node.getRightHandSide();
|
||||||
|
if (rhs.getNodeType() == ASTNode.SWITCH_EXPRESSION) {
|
||||||
|
switchNode[0] = rhs;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rhs.getNodeType() == ASTNode.SWITCH_STATEMENT) {
|
||||||
|
switchNode[0] = rhs;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (switchNode[0] != null) {
|
||||||
|
if (switchNode[0] instanceof SwitchExpression se) {
|
||||||
|
String evaluated = constantResolver.evaluateSwitchWithParams(se, parameterValues, context);
|
||||||
|
if (evaluated != null) return evaluated;
|
||||||
|
} else if (switchNode[0] instanceof SwitchStatement ss) {
|
||||||
|
String evaluated = constantResolver.evaluateSwitchWithParams(ss, parameterValues, context);
|
||||||
|
if (evaluated != null) return evaluated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> buildParameterValuesMap(String caller, String target, Map<String, List<CallEdge>> callGraph, List<String> path, int pathIndex) {
|
||||||
|
Map<String, String> paramValues = new HashMap<>();
|
||||||
|
List<CallEdge> edges = callGraph.get(caller);
|
||||||
|
if (edges == null) {
|
||||||
|
return paramValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We also want to support heuristic matches using a helper if isHeuristicMatch is not directly available, but let's check
|
||||||
|
// We can check if name equals or if it resolves to target
|
||||||
|
for (CallEdge edge : edges) {
|
||||||
|
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
||||||
|
List<String> args = edge.getArguments();
|
||||||
|
if (args == null || args.isEmpty()) break;
|
||||||
|
|
||||||
|
int lastDot = target.lastIndexOf('.');
|
||||||
|
if (lastDot < 0) break;
|
||||||
|
String className = target.substring(0, lastDot);
|
||||||
|
String methodName = target.substring(lastDot + 1);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td == null) break;
|
||||||
|
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md == null) break;
|
||||||
|
|
||||||
|
List<String> paramNames = new ArrayList<>();
|
||||||
|
for (Object paramObj : md.parameters()) {
|
||||||
|
SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj;
|
||||||
|
paramNames.add(param.getName().getIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
int minSize = Math.min(paramNames.size(), args.size());
|
||||||
|
for (int j = 0; j < minSize; j++) {
|
||||||
|
String argValue = args.get(j);
|
||||||
|
String resolvedArgValue = resolveArgumentValue(argValue, caller, path, pathIndex, callGraph);
|
||||||
|
paramValues.put(paramNames.get(j), resolvedArgValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paramValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveArgumentValue(String argValue, String callerMethod, List<String> path, int pathIndex, Map<String, List<CallEdge>> callGraph) {
|
||||||
|
if (argValue == null) return null;
|
||||||
|
if (argValue.contains(".") || argValue.startsWith("new ") || argValue.matches(".*[0-9].*")) {
|
||||||
|
return argValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int callerIndex = path.indexOf(callerMethod);
|
||||||
|
if (callerIndex <= 0) return argValue;
|
||||||
|
|
||||||
|
String prevCaller = path.get(callerIndex - 1);
|
||||||
|
List<CallEdge> prevEdges = callGraph.get(prevCaller);
|
||||||
|
if (prevEdges == null) return argValue;
|
||||||
|
|
||||||
|
for (CallEdge edge : prevEdges) {
|
||||||
|
if (edge.getTargetMethod().equals(callerMethod) || isHeuristicMatch(edge.getTargetMethod(), callerMethod)) {
|
||||||
|
List<String> prevArgs = edge.getArguments();
|
||||||
|
if (prevArgs == null || prevArgs.isEmpty()) break;
|
||||||
|
|
||||||
|
int lastDot = callerMethod.lastIndexOf('.');
|
||||||
|
if (lastDot < 0) break;
|
||||||
|
String className = callerMethod.substring(0, lastDot);
|
||||||
|
String methodName = callerMethod.substring(lastDot + 1);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td == null) break;
|
||||||
|
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md == null) break;
|
||||||
|
|
||||||
|
List<String> paramNames = new ArrayList<>();
|
||||||
|
for (Object paramObj : md.parameters()) {
|
||||||
|
SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj;
|
||||||
|
paramNames.add(param.getName().getIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
int paramIdx = paramNames.indexOf(argValue);
|
||||||
|
if (paramIdx >= 0 && paramIdx < prevArgs.size()) {
|
||||||
|
String prevArg = prevArgs.get(paramIdx);
|
||||||
|
return resolveArgumentValue(prevArg, prevCaller, path, pathIndex, callGraph);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return argValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHeuristicMatch(String neighbor, String target) {
|
||||||
|
if (neighbor == null || target == null) return false;
|
||||||
|
if (neighbor.equals(target)) return true;
|
||||||
|
|
||||||
|
if (neighbor.contains(".") && target.contains(".")) {
|
||||||
|
String methodNeighbor = neighbor.substring(neighbor.lastIndexOf('.') + 1);
|
||||||
|
String methodTarget = target.substring(target.lastIndexOf('.') + 1);
|
||||||
|
if (!methodNeighbor.equals(methodTarget)) return false;
|
||||||
|
|
||||||
|
String classNeighbor = neighbor.substring(0, neighbor.lastIndexOf('.'));
|
||||||
|
String classTarget = target.substring(0, target.lastIndexOf('.'));
|
||||||
|
if (classNeighbor.equals(classTarget)) return true;
|
||||||
|
|
||||||
|
String simpleClassNeighbor = classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||||
|
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||||
|
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||||
|
|
||||||
|
List<String> impls = context.getImplementations(classTarget);
|
||||||
|
if (impls != null && impls.contains(classNeighbor)) return true;
|
||||||
|
|
||||||
|
List<String> implsN = context.getImplementations(classNeighbor);
|
||||||
|
if (implsN != null && implsN.contains(classTarget)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return (MethodDeclaration) parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeDeclaration findEnclosingType(ASTNode node) {
|
||||||
|
ASTNode parent = node.getParent();
|
||||||
|
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
|
return (TypeDeclaration) parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
|
||||||
|
if (depth > 5) return mi;
|
||||||
|
if (mi.getExpression() != null) {
|
||||||
|
String exprStr = mi.getExpression().toString();
|
||||||
|
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects")) {
|
||||||
|
return mi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mi.arguments().isEmpty()) {
|
||||||
|
String mName = mi.getName().getIdentifier();
|
||||||
|
String lowerName = mName.toLowerCase();
|
||||||
|
|
||||||
|
if (lowerName.startsWith("map") || lowerName.startsWith("parse") ||
|
||||||
|
lowerName.startsWith("convert") || lowerName.startsWith("to") ||
|
||||||
|
lowerName.startsWith("resolve") || lowerName.startsWith("build") ||
|
||||||
|
lowerName.startsWith("create") || lowerName.startsWith("from") ||
|
||||||
|
lowerName.startsWith("transform") || lowerName.startsWith("translate") ||
|
||||||
|
lowerName.startsWith("derive") || lowerName.startsWith("determine") ||
|
||||||
|
lowerName.startsWith("calculate") || lowerName.startsWith("decode") ||
|
||||||
|
lowerName.startsWith("extract") || lowerName.startsWith("get") ||
|
||||||
|
lowerName.startsWith("find")) {
|
||||||
|
return mi;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression arg = (Expression) mi.arguments().get(0);
|
||||||
|
if (arg instanceof MethodInvocation innerMi) {
|
||||||
|
return unwrapMethodInvocation(innerMi, depth + 1);
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
return mi;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user