te2
This commit is contained in:
@@ -21,6 +21,11 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
this.constantResolver = new ConstantResolver();
|
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) {
|
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
|
||||||
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
||||||
List<CallChain> chains = new ArrayList<>();
|
List<CallChain> chains = new ArrayList<>();
|
||||||
@@ -986,11 +991,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
* field assignments found in the matching constructor body.
|
* field assignments found in the matching constructor body.
|
||||||
*
|
*
|
||||||
* <p>Handles both {@code this.field = param} and bare {@code field = param} assignment forms.
|
* <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(
|
protected java.util.Map<String, String> buildFieldValuesFromCIC(
|
||||||
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
||||||
org.eclipse.jdt.core.dom.ClassInstanceCreation cic,
|
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
|
// 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());
|
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.Map<String, String> fieldValues = new java.util.HashMap<>();
|
||||||
java.util.Set<String> visitedCtors = new java.util.HashSet<>();
|
java.util.Set<String> visitedCtors = new java.util.HashSet<>();
|
||||||
buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors);
|
buildFieldValuesFromCICRecursive(actualTd, cic, context, fieldValues, visitedCtors, rhsResolver);
|
||||||
return fieldValues;
|
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(
|
private void buildFieldValuesFromCICRecursive(
|
||||||
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
org.eclipse.jdt.core.dom.TypeDeclaration td,
|
||||||
org.eclipse.jdt.core.dom.ClassInstanceCreation cic,
|
org.eclipse.jdt.core.dom.ClassInstanceCreation cic,
|
||||||
CodebaseContext context,
|
CodebaseContext context,
|
||||||
java.util.Map<String, String> fieldValues,
|
java.util.Map<String, String> fieldValues,
|
||||||
java.util.Set<String> visitedCtors) {
|
java.util.Set<String> visitedCtors,
|
||||||
|
RhsResolver rhsResolver) {
|
||||||
|
|
||||||
String tdKey = context.getFqn(td);
|
String tdKey = context.getFqn(td);
|
||||||
if (!visitedCtors.add(tdKey)) return;
|
if (!visitedCtors.add(tdKey)) return;
|
||||||
@@ -1053,8 +1072,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
fieldName = sn.getIdentifier();
|
fieldName = sn.getIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldName != null && rhs instanceof org.eclipse.jdt.core.dom.SimpleName snRhs) {
|
if (fieldName != null) {
|
||||||
String paramVal = paramValues.get(snRhs.getIdentifier());
|
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) {
|
if (paramVal != null) {
|
||||||
fieldValues.put(fieldName, paramVal);
|
fieldValues.put(fieldName, paramVal);
|
||||||
fieldValues.put("this." + fieldName, paramVal);
|
fieldValues.put("this." + fieldName, paramVal);
|
||||||
@@ -1072,7 +1097,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (superFqn != null) {
|
if (superFqn != null) {
|
||||||
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
if (superTd != null) {
|
if (superTd != null) {
|
||||||
processSuperConstructorArgs(superTd, sci.arguments(), paramValues, context, fieldValues, visitedCtors);
|
processSuperConstructorArgs(superTd, sci.arguments(), paramValues, context, fieldValues, visitedCtors, rhsResolver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(sci);
|
return super.visit(sci);
|
||||||
@@ -1088,7 +1113,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
java.util.Map<String, String> subClassParamValues,
|
java.util.Map<String, String> subClassParamValues,
|
||||||
CodebaseContext context,
|
CodebaseContext context,
|
||||||
java.util.Map<String, String> fieldValues,
|
java.util.Map<String, String> fieldValues,
|
||||||
java.util.Set<String> visitedCtors) {
|
java.util.Set<String> visitedCtors,
|
||||||
|
RhsResolver rhsResolver) {
|
||||||
|
|
||||||
if (superTd == null) return;
|
if (superTd == null) return;
|
||||||
|
|
||||||
@@ -1141,8 +1167,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
fieldName = sn.getIdentifier();
|
fieldName = sn.getIdentifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldName != null && rhs instanceof org.eclipse.jdt.core.dom.SimpleName snRhs) {
|
if (fieldName != null) {
|
||||||
String paramVal = superParamValues.get(snRhs.getIdentifier());
|
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) {
|
if (paramVal != null) {
|
||||||
fieldValues.put(fieldName, paramVal);
|
fieldValues.put(fieldName, paramVal);
|
||||||
fieldValues.put("this." + fieldName, paramVal);
|
fieldValues.put("this." + fieldName, paramVal);
|
||||||
@@ -1160,7 +1192,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (superFqn != null) {
|
if (superFqn != null) {
|
||||||
TypeDeclaration nextSuperTd = context.getTypeDeclaration(superFqn);
|
TypeDeclaration nextSuperTd = context.getTypeDeclaration(superFqn);
|
||||||
if (nextSuperTd != null) {
|
if (nextSuperTd != null) {
|
||||||
processSuperConstructorArgs(nextSuperTd, sci.arguments(), superParamValues, context, fieldValues, visitedCtors);
|
processSuperConstructorArgs(nextSuperTd, sci.arguments(), superParamValues, context, fieldValues, visitedCtors, rhsResolver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(sci);
|
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
|
* Searches for a {@code return new SomeType(...)} statement in the given method, recursing
|
||||||
* into interface implementations when the method body is absent.
|
* into interface implementations when the method body is absent.
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ 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);
|
java.util.Map<String, String> fieldValues = buildFieldValuesFromCIC(varTypeTd, cic, context, this::evaluateMethodCallInConstructor);
|
||||||
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
|
||||||
@@ -810,8 +810,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: initExpr=" + initExpr + " class=" + (initExpr != null ? initExpr.getClass().getName() : "null"));
|
System.out.println("DEBUG evaluateGetterOnReceiver: initExpr=" + initExpr + " class=" + (initExpr != null ? initExpr.getClass().getName() : "null"));
|
||||||
if (initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
if (initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||||
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context));
|
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||||
extractFieldValuesFromConstructorBody(td, cic, fieldValues);
|
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: fieldValues=" + fieldValues);
|
System.out.println("DEBUG evaluateGetterOnReceiver: fieldValues=" + fieldValues);
|
||||||
int lastDot = entryMethod.lastIndexOf('.');
|
int lastDot = entryMethod.lastIndexOf('.');
|
||||||
if (lastDot > 0) {
|
if (lastDot > 0) {
|
||||||
@@ -872,102 +871,4 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
});
|
});
|
||||||
return initExpr[0];
|
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