more tests fixes

This commit is contained in:
2026-06-27 18:57:30 +02:00
parent 016f0e9b21
commit 5c7cf3639b
4 changed files with 108 additions and 32 deletions

View File

@@ -93,9 +93,9 @@ public class ConstantResolver {
// Fallback for unresolved AST nodes // Fallback for unresolved AST nodes
if (mi.getExpression() instanceof QualifiedName qn) { 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) { } 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); if (paramValues.containsKey(name)) return paramValues.get(name);
return null; return null;
} else if (expr instanceof QualifiedName qn) { } else if (expr instanceof QualifiedName qn) {
return qn.getName().getIdentifier(); return null;
} else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) { } else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) {
return sl.getLiteralValue(); return sl.getLiteralValue();
} }

View File

@@ -252,6 +252,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
exprParser.setKind(ASTParser.K_EXPRESSION); exprParser.setKind(ASTParser.K_EXPRESSION);
exprParser.setSource(resolvedValue.toCharArray()); exprParser.setSource(resolvedValue.toCharArray());
ASTNode exprNode = exprParser.createAST(null); ASTNode exprNode = exprParser.createAST(null);
System.out.println("DEBUG resolvedValue=" + resolvedValue + ", exprNode=" + exprNode.getClass().getSimpleName());
String varName = null; String varName = null;
String methodName = null; String methodName = null;
@@ -434,7 +435,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.'))); TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.')));
CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null; CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
List<String> values = constantExtractor.resolveMethodReturnConstant(declaredType, methodName, 0, new HashSet<>(), cu); List<String> 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); polymorphicEvents.addAll(values);
} }
if (polymorphicEvents.isEmpty() && variableTracer != null) { if (polymorphicEvents.isEmpty() && variableTracer != null) {
@@ -475,6 +476,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
if (currentTd != null && context.findMethodDeclaration(currentTd, methodName, true) != null) { if (currentTd != null && context.findMethodDeclaration(currentTd, methodName, true) != null) {
CompilationUnit cu = currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null; CompilationUnit cu = currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
List<String> values = constantExtractor.resolveMethodReturnConstant(context.getFqn(currentTd), methodName, 0, new HashSet<>(), cu); List<String> 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()) { if (values != null && !values.isEmpty()) {
polymorphicEvents.addAll(values); polymorphicEvents.addAll(values);
sourceMethod = methodFqn; sourceMethod = methodFqn;
@@ -669,7 +671,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
val = expr.toString(); 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) { protected String postProcessResolvedArgument(Expression originalExpr, String resolvedValue) {
@@ -690,6 +692,27 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
return null; 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) { protected Expression traceVariable(Expression expr) {
return variableTracer.traceVariable(expr); return variableTracer.traceVariable(expr);
} }

View File

@@ -143,7 +143,8 @@ public class ConstructorAnalyzer {
TypeDeclaration td, TypeDeclaration td,
ClassInstanceCreation cic, ClassInstanceCreation cic,
CodebaseContext context, CodebaseContext context,
RhsResolver rhsResolver) { RhsResolver rhsResolver,
Map<String, String> initialLocals) {
String actualTypeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); String actualTypeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
TypeDeclaration actualTd = context.getTypeDeclaration(actualTypeName); TypeDeclaration actualTd = context.getTypeDeclaration(actualTypeName);
@@ -153,15 +154,23 @@ public class ConstructorAnalyzer {
Map<String, String> fieldValues = new HashMap<>(); Map<String, String> fieldValues = new HashMap<>();
Set<String> visitedCtors = new HashSet<>(); Set<String> visitedCtors = new HashSet<>();
buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver); buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver, initialLocals);
return fieldValues; return fieldValues;
} }
public Map<String, String> buildFieldValuesFromCIC(
TypeDeclaration td,
ClassInstanceCreation cic,
CodebaseContext context,
RhsResolver rhsResolver) {
return buildFieldValuesFromCIC(td, cic, context, rhsResolver, null);
}
public Map<String, String> buildFieldValuesFromCIC( public Map<String, String> buildFieldValuesFromCIC(
TypeDeclaration td, TypeDeclaration td,
ClassInstanceCreation cic, ClassInstanceCreation cic,
CodebaseContext context) { CodebaseContext context) {
return buildFieldValuesFromCIC(td, cic, context, null); return buildFieldValuesFromCIC(td, cic, context, null, null);
} }
private void buildFieldValuesFromCICRecursive( private void buildFieldValuesFromCICRecursive(
@@ -170,7 +179,8 @@ public class ConstructorAnalyzer {
CodebaseContext context, CodebaseContext context,
Map<String, String> fieldValues, Map<String, String> fieldValues,
Set<String> visitedCtors, Set<String> visitedCtors,
RhsResolver rhsResolver) { RhsResolver rhsResolver,
Map<String, String> initialLocals) {
String tdKey = context.getFqn(td); String tdKey = context.getFqn(td);
if (!visitedCtors.add(tdKey)) return; if (!visitedCtors.add(tdKey)) return;
@@ -190,7 +200,13 @@ public class ConstructorAnalyzer {
if (!consts.isEmpty()) { if (!consts.isEmpty()) {
paramValues.put(pName, consts.get(0)); paramValues.put(pName, consts.get(0));
} else { } 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); if (resolved != null) paramValues.put(pName, resolved);
} }
} }
@@ -240,6 +256,12 @@ public class ConstructorAnalyzer {
} }
return super.visit(sci); return super.visit(sci);
} }
@Override
public boolean visit(ConstructorInvocation ci) {
processSuperConstructorArgs(td, ci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
return super.visit(ci);
}
}); });
break; break;
} }
@@ -330,6 +352,12 @@ public class ConstructorAnalyzer {
} }
return super.visit(sci); return super.visit(sci);
} }
@Override
public boolean visit(ConstructorInvocation ci) {
processSuperConstructorArgs(superTd, ci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
return super.visit(ci);
}
}); });
break; break;
} }

View File

@@ -179,23 +179,9 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
TypeDeclaration td = context.getTypeDeclaration(className); TypeDeclaration td = context.getTypeDeclaration(className);
if (td == null) return null; if (td == null) return null;
MethodDeclaration md = context.findMethodDeclaration(td, methodName, false); String resolvedMethodFqn = resolveMethodInTypeHierarchy(td, methodName);
if (md != null) { if (resolvedMethodFqn != null) {
return className; return resolvedMethodFqn.substring(0, resolvedMethodFqn.lastIndexOf('.'));
}
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;
}
} }
return null; return null;
} }
@@ -207,7 +193,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
if (receiver == null) { if (receiver == null) {
TypeDeclaration td = findEnclosingType(node); TypeDeclaration td = findEnclosingType(node);
if (td != null) { if (td != null) {
return resolveMethodInType(td, methodName); return resolveMethodInTypeHierarchy(td, methodName);
} }
return null; return null;
} }
@@ -233,12 +219,31 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
return receiverName + "." + methodName; 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; return null;
} }
protected String resolveReceiverTypeFallback(SimpleName receiverNameNode) { protected String resolveReceiverTypeFallback(SimpleName receiverNameNode) {
String varName = receiverNameNode.getIdentifier(); 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 // 1. Check local variables in enclosing method
MethodDeclaration enclosingMethod = findEnclosingMethod(receiverNameNode); MethodDeclaration enclosingMethod = findEnclosingMethod(receiverNameNode);
if (enclosingMethod != null) { if (enclosingMethod != null) {
@@ -357,12 +362,17 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
// Obtain the ClassInstanceCreation that created the object // Obtain the ClassInstanceCreation that created the object
org.eclipse.jdt.core.dom.ClassInstanceCreation cic = null; org.eclipse.jdt.core.dom.ClassInstanceCreation cic = null;
String receiverType = null;
if (initExpr[0] instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation directCic) { if (initExpr[0] instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation directCic) {
cic = directCic; cic = directCic;
} else if (initExpr[0] instanceof MethodInvocation factoryMi } else if (initExpr[0] instanceof MethodInvocation factoryMi
&& factoryMi.getExpression() instanceof SimpleName receiverSn) { && (factoryMi.getExpression() instanceof SimpleName || factoryMi.getExpression() instanceof QualifiedName)) {
// e.g. "mapper.toMsg(dto)" → find the CIC returned by the factory method // e.g. "mapper.toMsg(dto)" or "com.example.Factory.toMsg()"
String receiverType = resolveReceiverTypeFallback(receiverSn); if (factoryMi.getExpression() instanceof SimpleName receiverSn) {
receiverType = resolveReceiverTypeFallback(receiverSn);
} else if (factoryMi.getExpression() instanceof QualifiedName qn) {
receiverType = qn.getFullyQualifiedName();
}
if (receiverType != null) { if (receiverType != null) {
cic = findReturnedCIC(receiverType, factoryMi.getName().getIdentifier(), context); cic = findReturnedCIC(receiverType, factoryMi.getName().getIdentifier(), context);
} }
@@ -392,7 +402,22 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
if (varTypeTd == null) return null; if (varTypeTd == null) return null;
// Evaluate the getter body with field values substituted from the CIC's constructor args // Evaluate the getter body with field values substituted from the CIC's constructor args
java.util.Map<String, String> fieldValues = buildFieldValuesFromCIC(varTypeTd, cic, context, this::evaluateMethodCallInConstructor); java.util.Map<String, String> 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<String, String> fieldValues = constructorAnalyzer.buildFieldValuesFromCIC(varTypeTd, cic, context, this::evaluateMethodCallInConstructor, initialLocals);
if (fieldValues.isEmpty()) return null; if (fieldValues.isEmpty()) return null;
// Track setter calls on the variable between its initialization and the getter call // Track setter calls on the variable between its initialization and the getter call