Trace Supplier lambda arguments across call frames so JMS cancel chains resolve the concrete event.

When callee code calls eventProvider.get(), walk back to the caller's Supplier argument instead of stopping at the parameter name; also skip Supplier-vs-enum type checks for already-resolved lambda/enum call-site arguments under JDT bindings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 20:16:58 +02:00
parent 024458d638
commit c7e826c0fb
4 changed files with 302 additions and 15 deletions

View File

@@ -334,18 +334,20 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
}
if (expectedType != null && paramIndex < edge.getArguments().size()) {
String argValue = edge.getArguments().get(paramIndex);
String actualType = null;
if (argValue.contains(".") && !argValue.contains("(")) {
String prefix = argValue.substring(0, argValue.lastIndexOf('.'));
if (!prefix.equals("this") && !prefix.equals("super")) {
actualType = prefix;
if (!isProvablyResolvedCallSiteArgument(argValue, expectedType)) {
String actualType = null;
if (argValue.contains(".") && !argValue.contains("(")) {
String prefix = argValue.substring(0, argValue.lastIndexOf('.'));
if (!prefix.equals("this") && !prefix.equals("super")) {
actualType = prefix;
}
}
if (actualType == null && !argValue.contains(".")) {
actualType = variableTracer.getVariableDeclaredType(caller, argValue);
}
if (actualType != null && !typeResolver.isTypeCompatible(actualType, expectedType)) {
continue;
}
}
if (actualType == null && !argValue.contains(".")) {
actualType = variableTracer.getVariableDeclaredType(caller, argValue);
}
if (actualType != null && !typeResolver.isTypeCompatible(actualType, expectedType)) {
continue;
}
}
if (paramIndex < edge.getArguments().size()) {
@@ -3047,6 +3049,20 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
private void addConstantsFromTracedExpression(
Expression expr, List<String> constants, List<String> path, Map<String, List<CallEdge>> callGraph, String scopeMethod) {
if (expr instanceof MethodInvocation mi
&& "get".equals(mi.getName().getIdentifier())
&& mi.arguments().isEmpty()
&& !expressionAccessClassifier.isKeyedLookup(mi, scopeMethod)) {
List<String> callerFrameConstants = resolveSupplierConstantsFromCallerFrame(mi, path, callGraph, scopeMethod);
if (!callerFrameConstants.isEmpty()) {
for (String constant : callerFrameConstants) {
if (!constants.contains(constant)) {
constants.add(constant);
}
}
return;
}
}
List<Expression> traced = variableTracer.traceVariableAll(expr);
for (Expression tracedExpr : traced) {
addConstantsFromSingleExpression(tracedExpr, constants, path, callGraph, scopeMethod);
@@ -3470,6 +3486,102 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
return name.matches("[A-Z_][A-Z0-9_]*");
}
private boolean isProvablyResolvedCallSiteArgument(String argValue, String expectedType) {
if (argValue == null || argValue.isBlank() || expectedType == null) {
return false;
}
if (!isFunctionalInterfaceType(expectedType)) {
return false;
}
if (looksLikeEnumConstant(argValue)) {
return true;
}
return isLambdaArgument(argValue);
}
private boolean isLambdaArgument(String argValue) {
String trimmed = argValue.trim();
return trimmed.contains("->")
&& (trimmed.startsWith("(") || trimmed.matches("^\\w+\\s*->.*"));
}
private boolean isFunctionalInterfaceType(String typeName) {
if (typeName == null || typeName.isBlank()) {
return false;
}
String simple = typeName.contains(".") ? typeName.substring(typeName.lastIndexOf('.') + 1) : typeName;
if (simple.contains("<")) {
simple = simple.substring(0, simple.indexOf('<'));
}
return "Supplier".equals(simple) || "Function".equals(simple) || "Callable".equals(simple);
}
private List<String> resolveSupplierConstantsFromCallerFrame(
MethodInvocation getCall,
List<String> path,
Map<String, List<CallEdge>> callGraph,
String scopeMethod) {
if (path == null || path.size() < 2 || scopeMethod == null || getCall.getExpression() == null) {
return List.of();
}
if (!(getCall.getExpression() instanceof SimpleName supplierParam)) {
return List.of();
}
int scopeIndex = -1;
for (int i = path.size() - 1; i >= 0; i--) {
if (scopeMethod.equals(path.get(i))) {
scopeIndex = i;
break;
}
}
if (scopeIndex <= 0) {
return List.of();
}
String callee = path.get(scopeIndex);
String caller = path.get(scopeIndex - 1);
int paramIndex = typeResolver.getParameterIndex(callee, supplierParam.getIdentifier());
if (paramIndex < 0) {
return List.of();
}
List<CallEdge> edges = callGraph.get(caller);
if (edges == null) {
return List.of();
}
for (CallEdge edge : edges) {
if (!edge.getTargetMethod().equals(callee) && !pathFinder.isHeuristicMatch(edge.getTargetMethod(), callee)) {
continue;
}
if (paramIndex >= edge.getArguments().size()) {
continue;
}
return extractConstantsFromResolvedCallSiteArgument(edge.getArguments().get(paramIndex));
}
return List.of();
}
private List<String> extractConstantsFromResolvedCallSiteArgument(String argValue) {
if (argValue == null || argValue.isBlank()) {
return List.of();
}
ASTNode parsed = parseExpressionString(argValue);
if (!(parsed instanceof Expression expr)) {
if (looksLikeEnumConstant(argValue)) {
return List.of(argValue);
}
return List.of();
}
String resolved = resolveArgument(expr);
ASTNode resolvedNode = parseExpressionString(resolved);
List<String> constants = new ArrayList<>();
if (resolvedNode instanceof Expression resolvedExpr) {
constantExtractor.extractConstantsFromExpression(resolvedExpr, constants);
}
if (constants.isEmpty() && looksLikeEnumConstant(resolved)) {
constants.add(resolved);
}
return constants;
}
private void trackKeyedMapLookupFlags(
Expression expr,
List<String> path,