This commit is contained in:
2026-06-24 18:25:36 +02:00
parent ae75379f77
commit 1b1e036680
3 changed files with 116 additions and 132 deletions

View File

@@ -1652,6 +1652,81 @@ import java.util.*;
return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal; return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal;
} }
/**
* Resolves a single argument expression to a string value.
* Handles lambdas, method references, and constructor argument unwrapping.
* Subclasses can override {@link #postProcessResolvedArgument(Expression, String)} for custom behavior.
*/
protected String resolveArgument(org.eclipse.jdt.core.dom.Expression expr) {
// Extract from lambda
if (expr instanceof org.eclipse.jdt.core.dom.LambdaExpression le) {
org.eclipse.jdt.core.dom.ASTNode body = le.getBody();
if (body instanceof org.eclipse.jdt.core.dom.Expression bodyExpr) {
expr = bodyExpr;
} else if (body instanceof org.eclipse.jdt.core.dom.Block block) {
for (Object stmtObj : block.statements()) {
if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
// Extract from method reference
if (expr instanceof org.eclipse.jdt.core.dom.ExpressionMethodReference emr) {
org.eclipse.jdt.core.dom.TypeDeclaration td = findEnclosingType(emr);
if (td != null) {
org.eclipse.jdt.core.dom.MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true);
if (md != null && md.getBody() != null) {
for (Object stmtObj : md.getBody().statements()) {
if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
}
// Unwrap constructor first argument if it's a constant (e.g., new CustomMessage(OrderEvent.PROCESS))
if (expr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic && !cic.arguments().isEmpty()) {
org.eclipse.jdt.core.dom.Expression firstArg = (org.eclipse.jdt.core.dom.Expression) cic.arguments().get(0);
String resolved = constantResolver.resolve(firstArg, context);
if (resolved != null) {
expr = firstArg;
}
}
String val = constantResolver.resolve(expr, context);
if (val == null) {
val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc)
}
// Allow subclasses to post-process (e.g., preserve getters for later substitution)
return postProcessResolvedArgument(expr, val);
}
/**
* Hook for subclasses to customize argument resolution.
* Default implementation returns the value as-is.
*/
protected String postProcessResolvedArgument(org.eclipse.jdt.core.dom.Expression originalExpr, String resolvedValue) {
return resolvedValue;
}
/**
* Resolves a list of argument expressions.
* Default implementation calls {@link #resolveArgument(Expression)} for each.
*/
protected List<String> resolveArguments(List<?> astArguments) {
List<String> args = new ArrayList<>();
for (Object argObj : astArguments) {
args.add(resolveArgument((org.eclipse.jdt.core.dom.Expression) argObj));
}
return args;
}
protected abstract Map<String, List<CallEdge>> buildCallGraph(); protected abstract Map<String, List<CallEdge>> buildCallGraph();
protected abstract String resolveCalledMethod(MethodInvocation node); protected abstract String resolveCalledMethod(MethodInvocation node);
protected List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) { protected List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {

View File

@@ -122,80 +122,31 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
return graph; return graph;
} }
protected List<String> resolveArguments(List<?> astArguments) { @Override
List<String> args = new ArrayList<>(); protected String postProcessResolvedArgument(org.eclipse.jdt.core.dom.Expression originalExpr, String resolvedValue) {
for (Object argObj : astArguments) {
Expression expr = (Expression) argObj;
// Extract from lambda
if (expr instanceof LambdaExpression le) {
ASTNode body = le.getBody();
if (body instanceof Expression bodyExpr) {
expr = bodyExpr;
} else if (body instanceof Block block) {
for (Object stmtObj : block.statements()) {
if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
if (expr instanceof ExpressionMethodReference emr) {
TypeDeclaration td = findEnclosingType(expr);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true);
if (md != null && md.getBody() != null) {
for (Object stmtObj : md.getBody().statements()) {
if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
}
// Removed eager tracing here to allow dynamic tracing in resolveTriggerPointParameters
if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) {
Expression firstArg = (Expression) cic.arguments().get(0);
String resolved = constantResolver.resolve(firstArg, context);
if (resolved != null) {
expr = firstArg; // Only unwrap if it's actually a constant
}
}
String val = null;
// Preserve getter calls on 'this' or 'super' for later resolution in trigger parameter tracing // Preserve getter calls on 'this' or 'super' for later resolution in trigger parameter tracing
// where we can substitute the actual receiver from the call edge // where we can substitute the actual receiver from the call edge
boolean isThisOrSuperGetter = expr instanceof MethodInvocation mi boolean isThisOrSuperGetter = originalExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi
&& (mi.getExpression() instanceof ThisExpression || mi.getExpression() instanceof SuperMethodInvocation) && (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression || mi.getExpression() instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation)
&& mi.arguments().isEmpty(); && mi.arguments().isEmpty();
if (!isThisOrSuperGetter) { if (isThisOrSuperGetter) {
return originalExpr.toString(); // Preserve "this.getTransitionType()" for later substitution
}
// Narrow resolution for "localVar.getX()" patterns where localVar is initialized // Narrow resolution for "localVar.getX()" patterns where localVar is initialized
// from a ClassInstanceCreation (directly or via a factory method). This avoids // from a ClassInstanceCreation (directly or via a factory method). This avoids
// the broad ENUM_SET fallback when the getter transforms one enum to another. // the broad ENUM_SET fallback when the getter transforms one enum to another.
if (expr instanceof MethodInvocation getterMi if (originalExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation getterMi
&& getterMi.getExpression() instanceof SimpleName instanceSn && getterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName instanceSn
&& getterMi.arguments().isEmpty()) { && getterMi.arguments().isEmpty()) {
val = tryNarrowGetterViaLocalVarChain(getterMi, instanceSn); String narrowed = tryNarrowGetterViaLocalVarChain(getterMi, instanceSn);
if (narrowed != null) {
return narrowed;
}
} }
if (val == null) val = constantResolver.resolve(expr, context); return resolvedValue;
if (val == null) {
val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc)
}
} else {
val = expr.toString(); // Preserve "this.getTransitionType()" for later substitution
}
args.add(val);
}
System.out.println("DEBUG resolveArguments: " + args);
return args;
} }
protected List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) { protected List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {

View File

@@ -122,67 +122,25 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
return graph; return graph;
} }
private List<String> resolveArguments(List<?> astArguments) { @Override
List<String> args = new ArrayList<>(); protected String resolveArgument(org.eclipse.jdt.core.dom.Expression expr) {
for (Object argObj : astArguments) {
Expression expr = (Expression) argObj;
// Extract from lambda
if (expr instanceof LambdaExpression le) {
ASTNode body = le.getBody();
if (body instanceof Expression bodyExpr) {
expr = bodyExpr;
} else if (body instanceof Block block) {
for (Object stmtObj : block.statements()) {
if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
if (expr instanceof ExpressionMethodReference emr) {
TypeDeclaration td = findEnclosingType(expr);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true);
if (md != null && md.getBody() != null) {
for (Object stmtObj : md.getBody().statements()) {
if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
}
// Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...)) // Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...))
Expression unwrappedBuilder = unwrapMessageBuilder(expr); org.eclipse.jdt.core.dom.Expression unwrappedBuilder = unwrapMessageBuilder(expr);
if (unwrappedBuilder != expr) { if (unwrappedBuilder != expr) {
expr = unwrappedBuilder; expr = unwrappedBuilder;
} }
Expression tracedExpr = traceVariable(expr); // Trace variable to resolve local variable references
if (tracedExpr instanceof QualifiedName || tracedExpr instanceof ClassInstanceCreation || tracedExpr instanceof StringLiteral || tracedExpr instanceof NumberLiteral) { org.eclipse.jdt.core.dom.Expression tracedExpr = traceVariable(expr);
if (tracedExpr instanceof org.eclipse.jdt.core.dom.QualifiedName
|| tracedExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation
|| tracedExpr instanceof org.eclipse.jdt.core.dom.StringLiteral
|| tracedExpr instanceof org.eclipse.jdt.core.dom.NumberLiteral) {
expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor
} }
if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) { // Delegate to base class for lambda, method reference, and constructor unwrapping
Expression firstArg = (Expression) cic.arguments().get(0); return super.resolveArgument(expr);
String resolved = constantResolver.resolve(firstArg, context);
if (resolved != null) {
expr = firstArg; // Only unwrap if it's actually a constant
}
}
String val = constantResolver.resolve(expr, context);
if (val == null) {
val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc)
}
args.add(val);
}
return args;
} }
private List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) { private List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {