te2
This commit is contained in:
@@ -12,14 +12,19 @@ import org.eclipse.jdt.core.dom.*;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
protected final CodebaseContext context;
|
||||
protected final ConstantResolver constantResolver;
|
||||
public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
protected final CodebaseContext context;
|
||||
protected final ConstantResolver constantResolver;
|
||||
|
||||
public AbstractCallGraphEngine(CodebaseContext context) {
|
||||
this.context = context;
|
||||
this.constantResolver = new ConstantResolver();
|
||||
}
|
||||
public AbstractCallGraphEngine(CodebaseContext context) {
|
||||
this.context = context;
|
||||
this.constantResolver = new ConstantResolver();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
protected interface RhsResolver {
|
||||
String resolve(Expression rhs, Map<String, String> paramValues, TypeDeclaration td);
|
||||
}
|
||||
|
||||
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
|
||||
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
||||
@@ -986,11 +991,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
* field assignments found in the matching constructor body.
|
||||
*
|
||||
* <p>Handles both {@code this.field = param} and bare {@code field = param} assignment forms.
|
||||
*
|
||||
* @param rhsResolver optional strategy to resolve non-SimpleName RHS expressions (e.g., method calls)
|
||||
*/
|
||||
protected java.util.Map<String, String> buildFieldValuesFromCIC(
|
||||
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
||||
org.eclipse.jdt.core.dom.ClassInstanceCreation cic,
|
||||
CodebaseContext context) {
|
||||
CodebaseContext context,
|
||||
RhsResolver rhsResolver) {
|
||||
|
||||
// Use the CIC's actual type as the starting point, not the declared type
|
||||
String actualTypeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||
@@ -1001,16 +1009,27 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
|
||||
java.util.Map<String, String> fieldValues = new java.util.HashMap<>();
|
||||
java.util.Set<String> visitedCtors = new java.util.HashSet<>();
|
||||
buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors);
|
||||
buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver);
|
||||
return fieldValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward-compatible overload without RhsResolver.
|
||||
*/
|
||||
protected java.util.Map<String, String> buildFieldValuesFromCIC(
|
||||
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
||||
org.eclipse.jdt.core.dom.ClassInstanceCreation cic,
|
||||
CodebaseContext context) {
|
||||
return buildFieldValuesFromCIC(td, cic, context, null);
|
||||
}
|
||||
|
||||
private void buildFieldValuesFromCICRecursive(
|
||||
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
||||
org.eclipse.jdt.core.dom.ClassInstanceCreation cic,
|
||||
CodebaseContext context,
|
||||
java.util.Map<String, String> fieldValues,
|
||||
java.util.Set<String> visitedCtors) {
|
||||
java.util.Set<String> visitedCtors,
|
||||
RhsResolver rhsResolver) {
|
||||
|
||||
String tdKey = context.getFqn(td);
|
||||
if (!visitedCtors.add(tdKey)) return;
|
||||
@@ -1053,8 +1072,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
fieldName = sn.getIdentifier();
|
||||
}
|
||||
|
||||
if (fieldName != null && rhs instanceof org.eclipse.jdt.core.dom.SimpleName snRhs) {
|
||||
String paramVal = paramValues.get(snRhs.getIdentifier());
|
||||
if (fieldName != null) {
|
||||
String paramVal = null;
|
||||
if (rhs instanceof org.eclipse.jdt.core.dom.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);
|
||||
@@ -1072,7 +1097,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (superFqn != null) {
|
||||
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||
if (superTd != null) {
|
||||
processSuperConstructorArgs(superTd, sci.arguments(), paramValues, context, fieldValues, visitedCtors);
|
||||
processSuperConstructorArgs(superTd, sci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
|
||||
}
|
||||
}
|
||||
return super.visit(sci);
|
||||
@@ -1088,7 +1113,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
java.util.Map<String, String> subClassParamValues,
|
||||
CodebaseContext context,
|
||||
java.util.Map<String, String> fieldValues,
|
||||
java.util.Set<String> visitedCtors) {
|
||||
java.util.Set<String> visitedCtors,
|
||||
RhsResolver rhsResolver) {
|
||||
|
||||
if (superTd == null) return;
|
||||
|
||||
@@ -1141,8 +1167,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
fieldName = sn.getIdentifier();
|
||||
}
|
||||
|
||||
if (fieldName != null && rhs instanceof org.eclipse.jdt.core.dom.SimpleName snRhs) {
|
||||
String paramVal = superParamValues.get(snRhs.getIdentifier());
|
||||
if (fieldName != null) {
|
||||
String paramVal = null;
|
||||
if (rhs instanceof org.eclipse.jdt.core.dom.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);
|
||||
@@ -1160,7 +1192,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (superFqn != null) {
|
||||
TypeDeclaration nextSuperTd = context.getTypeDeclaration(superFqn);
|
||||
if (nextSuperTd != null) {
|
||||
processSuperConstructorArgs(nextSuperTd, sci.arguments(), superParamValues, context, fieldValues, visitedCtors);
|
||||
processSuperConstructorArgs(nextSuperTd, sci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
|
||||
}
|
||||
}
|
||||
return super.visit(sci);
|
||||
@@ -1170,6 +1202,40 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a method call expression found in a constructor body (e.g., {@code this.field = computeValue(param)}).
|
||||
* Handles both explicit {@code this.} and implicit {@code this} (null receiver) calls.
|
||||
*
|
||||
* @param rhs the expression to evaluate (expected to be a MethodInvocation)
|
||||
* @param paramValues map of constructor parameter names to their resolved values
|
||||
* @param td the type declaration containing the method
|
||||
* @return the resolved value, or {@code null} if evaluation is not possible
|
||||
*/
|
||||
protected String evaluateMethodCallInConstructor(
|
||||
org.eclipse.jdt.core.dom.Expression rhs,
|
||||
java.util.Map<String, String> paramValues,
|
||||
org.eclipse.jdt.core.dom.TypeDeclaration td) {
|
||||
|
||||
if (!(rhs instanceof org.eclipse.jdt.core.dom.MethodInvocation mi)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String methodName = mi.getName().getIdentifier();
|
||||
org.eclipse.jdt.core.dom.Expression receiver = mi.getExpression();
|
||||
// Handle both explicit 'this.' and implicit 'this' (null receiver)
|
||||
if (receiver != null && !(receiver instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
org.eclipse.jdt.core.dom.MethodDeclaration method = context.findMethodDeclaration(td, methodName, true);
|
||||
if (method == null || method.getBody() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
java.util.Map<String, String> localVars = new java.util.HashMap<>(paramValues);
|
||||
return constantResolver.evaluateMethodBodyWithLocals(method, localVars, context, new java.util.HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a {@code return new SomeType(...)} statement in the given method, recursing
|
||||
* into interface implementations when the method body is absent.
|
||||
|
||||
@@ -434,7 +434,7 @@ 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<String, String> fieldValues = buildFieldValuesFromCIC(varTypeTd, cic, context);
|
||||
java.util.Map<String, String> fieldValues = buildFieldValuesFromCIC(varTypeTd, cic, context, this::evaluateMethodCallInConstructor);
|
||||
if (fieldValues.isEmpty()) return null;
|
||||
|
||||
// Track setter calls on the variable between its initialization and the getter call
|
||||
@@ -810,8 +810,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||
System.out.println("DEBUG evaluateGetterOnReceiver: initExpr=" + initExpr + " class=" + (initExpr != null ? initExpr.getClass().getName() : "null"));
|
||||
if (initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context));
|
||||
extractFieldValuesFromConstructorBody(td, cic, fieldValues);
|
||||
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||
System.out.println("DEBUG evaluateGetterOnReceiver: fieldValues=" + fieldValues);
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
@@ -872,102 +871,4 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
});
|
||||
return initExpr[0];
|
||||
}
|
||||
|
||||
private void extractFieldValuesFromConstructorBody(TypeDeclaration td, ClassInstanceCreation cic, Map<String, String> fieldValues) {
|
||||
System.out.println("DEBUG extractFieldValuesFromConstructorBody: called for " + cic.getType());
|
||||
String actualTypeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||
TypeDeclaration actualTd = context.getTypeDeclaration(actualTypeName);
|
||||
if (actualTd == null) {
|
||||
actualTd = td;
|
||||
}
|
||||
final TypeDeclaration finalTd = actualTd;
|
||||
|
||||
for (MethodDeclaration ctor : finalTd.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<>();
|
||||
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 rhsValue = null;
|
||||
if (rhs instanceof SimpleName snRhs) {
|
||||
rhsValue = paramValues.get(snRhs.getIdentifier());
|
||||
System.out.println("DEBUG extractFieldValuesFromConstructorBody: found param assignment " + fieldName + "=" + rhsValue);
|
||||
} else if (rhs instanceof MethodInvocation mi) {
|
||||
rhsValue = evaluateMethodCallInConstructor(mi, paramValues, finalTd);
|
||||
System.out.println("DEBUG extractFieldValuesFromConstructorBody: found method call assignment " + fieldName + "=" + rhsValue);
|
||||
}
|
||||
|
||||
if (rhsValue != null) {
|
||||
fieldValues.put(fieldName, rhsValue);
|
||||
fieldValues.put("this." + fieldName, rhsValue);
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
break; // first matching constructor is enough
|
||||
}
|
||||
}
|
||||
|
||||
private String evaluateMethodCallInConstructor(MethodInvocation mi, Map<String, String> paramValues, TypeDeclaration td) {
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: called for " + mi.getName().getIdentifier() + " in " + context.getFqn(td));
|
||||
try {
|
||||
String methodName = mi.getName().getIdentifier();
|
||||
Expression receiver = mi.getExpression();
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: receiver=" + (receiver != null ? receiver.getClass().getName() : "null"));
|
||||
// Handle both explicit 'this.' and implicit 'this' (null receiver)
|
||||
if (receiver != null && !(receiver instanceof ThisExpression)) {
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: receiver not ThisExpression, returning null");
|
||||
return null;
|
||||
}
|
||||
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: before findMethodDeclaration");
|
||||
MethodDeclaration method = context.findMethodDeclaration(td, methodName, true);
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: method found=" + (method != null));
|
||||
if (method != null) {
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: method in " + context.getFqn(findEnclosingType(method)) + " body=" + (method.getBody() != null));
|
||||
}
|
||||
if (method == null || method.getBody() == null) {
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: method null or no body, returning null");
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, String> localVars = new HashMap<>(paramValues);
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: localVars=" + localVars);
|
||||
String result = constantResolver.evaluateMethodBodyWithLocals(method, localVars, context, new HashSet<>());
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: result=" + result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
System.err.println("DEBUG evaluateMethodCallInConstructor: EXCEPTION: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user