diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java index e7a0d59..d6ab77a 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java @@ -93,9 +93,9 @@ public class ConstantResolver { // Fallback for unresolved AST nodes if (mi.getExpression() instanceof QualifiedName qn) { - return qn.getName().getIdentifier(); + System.out.println("RETURNING qn name: " + qn.getName().getIdentifier()); return qn.getName().getIdentifier(); } else if (mi.getExpression() instanceof SimpleName sn) { - return sn.getIdentifier(); + System.out.println("RETURNING sn name: " + sn.getIdentifier()); return sn.getIdentifier(); } } @@ -418,7 +418,7 @@ public class ConstantResolver { if (paramValues.containsKey(name)) return paramValues.get(name); return null; } else if (expr instanceof QualifiedName qn) { - return qn.getName().getIdentifier(); + return null; } else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) { return sl.getLiteralValue(); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java index 74f99d7..6cb62cd 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -252,6 +252,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { exprParser.setKind(ASTParser.K_EXPRESSION); exprParser.setSource(resolvedValue.toCharArray()); ASTNode exprNode = exprParser.createAST(null); + System.out.println("DEBUG resolvedValue=" + resolvedValue + ", exprNode=" + exprNode.getClass().getSimpleName()); String varName = null; String methodName = null; @@ -434,7 +435,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.'))); CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null; List values = constantExtractor.resolveMethodReturnConstant(declaredType, methodName, 0, new HashSet<>(), cu); - if (values != null && !values.isEmpty()) { + System.out.println("RESOLVED VALUES FOR " + methodName + " in " + currentTd.getName() + ": " + values); if (values != null && !values.isEmpty()) { polymorphicEvents.addAll(values); } if (polymorphicEvents.isEmpty() && variableTracer != null) { @@ -475,6 +476,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { if (currentTd != null && context.findMethodDeclaration(currentTd, methodName, true) != null) { CompilationUnit cu = currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null; List values = constantExtractor.resolveMethodReturnConstant(context.getFqn(currentTd), methodName, 0, new HashSet<>(), cu); + System.out.println("DEBUG RESOLVED VALUES FOR " + methodName + " in " + currentTd.getName() + " -> " + values); if (values != null && !values.isEmpty()) { polymorphicEvents.addAll(values); sourceMethod = methodFqn; @@ -669,7 +671,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { val = expr.toString(); } - return postProcessResolvedArgument(expr, val); + System.out.println("DEBUG resolveArgument expr=" + expr + ", val=" + val); return postProcessResolvedArgument(expr, val); } protected String postProcessResolvedArgument(Expression originalExpr, String resolvedValue) { @@ -690,6 +692,27 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { return null; } + protected String resolveMethodInTypeHierarchy(TypeDeclaration td, String methodName) { + String resolved = resolveMethodInType(td, methodName); + if (resolved != null) { + return resolved; + } + String superFqn = context.getSuperclassFqn(td); + while (superFqn != null) { + TypeDeclaration superTd = context.getTypeDeclaration(superFqn); + if (superTd != null) { + resolved = resolveMethodInType(superTd, methodName); + if (resolved != null) { + return resolved; + } + superFqn = context.getSuperclassFqn(superTd); + } else { + break; + } + } + return null; + } + protected Expression traceVariable(Expression expr) { return variableTracer.traceVariable(expr); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java index aa76c0d..c175aec 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java @@ -143,7 +143,8 @@ public class ConstructorAnalyzer { TypeDeclaration td, ClassInstanceCreation cic, CodebaseContext context, - RhsResolver rhsResolver) { + RhsResolver rhsResolver, + Map initialLocals) { String actualTypeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); TypeDeclaration actualTd = context.getTypeDeclaration(actualTypeName); @@ -153,15 +154,23 @@ public class ConstructorAnalyzer { Map fieldValues = new HashMap<>(); Set visitedCtors = new HashSet<>(); - buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver); + buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver, initialLocals); return fieldValues; } + public Map buildFieldValuesFromCIC( + TypeDeclaration td, + ClassInstanceCreation cic, + CodebaseContext context, + RhsResolver rhsResolver) { + return buildFieldValuesFromCIC(td, cic, context, rhsResolver, null); + } + public Map buildFieldValuesFromCIC( TypeDeclaration td, ClassInstanceCreation cic, CodebaseContext context) { - return buildFieldValuesFromCIC(td, cic, context, null); + return buildFieldValuesFromCIC(td, cic, context, null, null); } private void buildFieldValuesFromCICRecursive( @@ -170,7 +179,8 @@ public class ConstructorAnalyzer { CodebaseContext context, Map fieldValues, Set visitedCtors, - RhsResolver rhsResolver) { + RhsResolver rhsResolver, + Map initialLocals) { String tdKey = context.getFqn(td); if (!visitedCtors.add(tdKey)) return; @@ -190,7 +200,13 @@ public class ConstructorAnalyzer { if (!consts.isEmpty()) { paramValues.put(pName, consts.get(0)); } else { - String resolved = constantResolver.resolve(argExpr, context); + String resolved = null; + if (argExpr instanceof SimpleName sn && initialLocals != null && initialLocals.containsKey(sn.getIdentifier())) { + resolved = initialLocals.get(sn.getIdentifier()); + } + if (resolved == null) { + resolved = constantResolver.resolve(argExpr, context); + } if (resolved != null) paramValues.put(pName, resolved); } } @@ -240,6 +256,12 @@ public class ConstructorAnalyzer { } return super.visit(sci); } + + @Override + public boolean visit(ConstructorInvocation ci) { + processSuperConstructorArgs(td, ci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver); + return super.visit(ci); + } }); break; } @@ -330,6 +352,12 @@ public class ConstructorAnalyzer { } return super.visit(sci); } + + @Override + public boolean visit(ConstructorInvocation ci) { + processSuperConstructorArgs(superTd, ci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver); + return super.visit(ci); + } }); break; } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java index aa0a7fd..6ed3040 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java @@ -179,23 +179,9 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { TypeDeclaration td = context.getTypeDeclaration(className); if (td == null) return null; - MethodDeclaration md = context.findMethodDeclaration(td, methodName, false); - if (md != null) { - return className; - } - - String superFqn = context.getSuperclassFqn(td); - while (superFqn != null) { - TypeDeclaration superTd = context.getTypeDeclaration(superFqn); - if (superTd != null) { - MethodDeclaration superMd = context.findMethodDeclaration(superTd, methodName, false); - if (superMd != null) { - return superFqn; - } - superFqn = context.getSuperclassFqn(superTd); - } else { - break; - } + String resolvedMethodFqn = resolveMethodInTypeHierarchy(td, methodName); + if (resolvedMethodFqn != null) { + return resolvedMethodFqn.substring(0, resolvedMethodFqn.lastIndexOf('.')); } return null; } @@ -207,7 +193,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { if (receiver == null) { TypeDeclaration td = findEnclosingType(node); if (td != null) { - return resolveMethodInType(td, methodName); + return resolveMethodInTypeHierarchy(td, methodName); } return null; } @@ -233,12 +219,31 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { return receiverName + "." + methodName; } + if (receiver instanceof QualifiedName qn) { + String fqn = qn.getFullyQualifiedName(); + TypeDeclaration td = context.getTypeDeclaration(fqn); + if (td != null) { + return fqn + "." + methodName; + } + return fqn + "." + methodName; + } + return null; } protected String resolveReceiverTypeFallback(SimpleName receiverNameNode) { String varName = receiverNameNode.getIdentifier(); + CompilationUnit cu = receiverNameNode.getRoot() instanceof CompilationUnit ? (CompilationUnit) receiverNameNode.getRoot() : null; + TypeDeclaration staticTd = context.getTypeDeclaration(varName, cu); + if (staticTd != null) { + return context.getFqn(staticTd); + } + TypeDeclaration fallbackStaticTd = context.getTypeDeclaration(varName); + if (fallbackStaticTd != null) { + return context.getFqn(fallbackStaticTd); + } + // 1. Check local variables in enclosing method MethodDeclaration enclosingMethod = findEnclosingMethod(receiverNameNode); if (enclosingMethod != null) { @@ -357,12 +362,17 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { // Obtain the ClassInstanceCreation that created the object org.eclipse.jdt.core.dom.ClassInstanceCreation cic = null; + String receiverType = null; if (initExpr[0] instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation directCic) { cic = directCic; } else if (initExpr[0] instanceof MethodInvocation factoryMi - && factoryMi.getExpression() instanceof SimpleName receiverSn) { - // e.g. "mapper.toMsg(dto)" → find the CIC returned by the factory method - String receiverType = resolveReceiverTypeFallback(receiverSn); + && (factoryMi.getExpression() instanceof SimpleName || factoryMi.getExpression() instanceof QualifiedName)) { + // e.g. "mapper.toMsg(dto)" or "com.example.Factory.toMsg()" + if (factoryMi.getExpression() instanceof SimpleName receiverSn) { + receiverType = resolveReceiverTypeFallback(receiverSn); + } else if (factoryMi.getExpression() instanceof QualifiedName qn) { + receiverType = qn.getFullyQualifiedName(); + } if (receiverType != null) { cic = findReturnedCIC(receiverType, factoryMi.getName().getIdentifier(), context); } @@ -392,7 +402,22 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { if (varTypeTd == null) return null; // Evaluate the getter body with field values substituted from the CIC's constructor args - java.util.Map fieldValues = buildFieldValuesFromCIC(varTypeTd, cic, context, this::evaluateMethodCallInConstructor); + java.util.Map initialLocals = new java.util.HashMap<>(); + if (initExpr[0] instanceof MethodInvocation factoryMi) { + MethodDeclaration md = context.findMethodDeclaration(context.getTypeDeclaration(receiverType != null ? receiverType : varTypeName[0]), factoryMi.getName().getIdentifier(), true); + if (md != null) { + for (int i = 0; i < md.parameters().size() && i < factoryMi.arguments().size(); i++) { + SingleVariableDeclaration param = (SingleVariableDeclaration) md.parameters().get(i); + Expression arg = (Expression) factoryMi.arguments().get(i); + String resolvedVal = constantResolver.resolve(arg, context); + if (resolvedVal != null) { + initialLocals.put(param.getName().getIdentifier(), resolvedVal); + } + } + } + } + + java.util.Map fieldValues = constructorAnalyzer.buildFieldValuesFromCIC(varTypeTd, cic, context, this::evaluateMethodCallInConstructor, initialLocals); if (fieldValues.isEmpty()) return null; // Track setter calls on the variable between its initialization and the getter call