edge case fixes
This commit is contained in:
@@ -261,6 +261,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (varName != null && declaredType == null) {
|
if (varName != null && declaredType == null) {
|
||||||
for (String methodFqn : path) {
|
for (String methodFqn : path) {
|
||||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||||
|
System.out.println("DEBUG getVariableDeclaredType(" + methodFqn + ", " + varName + ") = " + declaredType);
|
||||||
if (declaredType != null) {
|
if (declaredType != null) {
|
||||||
sourceMethod = methodFqn;
|
sourceMethod = methodFqn;
|
||||||
break;
|
break;
|
||||||
@@ -383,11 +384,12 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
if (td != null) {
|
if (td != null) {
|
||||||
|
String cleanFieldName = varName.startsWith("this.") ? varName.substring(5) : varName;
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
if (md != null) {
|
if (md != null) {
|
||||||
for (Object pObj : md.parameters()) {
|
for (Object pObj : md.parameters()) {
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
||||||
if (svd.getName().getIdentifier().equals(varName)) {
|
if (svd.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
return svd.getType().toString();
|
return svd.getType().toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,15 +400,71 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
public boolean visit(VariableDeclarationStatement node) {
|
public boolean visit(VariableDeclarationStatement node) {
|
||||||
for (Object fragObj : node.fragments()) {
|
for (Object fragObj : node.fragments()) {
|
||||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
if (frag.getName().getIdentifier().equals(varName)) {
|
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
foundType[0] = node.getType().toString();
|
foundType[0] = node.getType().toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.LambdaExpression node) {
|
||||||
|
for (Object paramObj : node.parameters()) {
|
||||||
|
org.eclipse.jdt.core.dom.VariableDeclaration param = (org.eclipse.jdt.core.dom.VariableDeclaration) paramObj;
|
||||||
|
if (param.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
if (param instanceof SingleVariableDeclaration svd && svd.getType() != null) {
|
||||||
|
foundType[0] = svd.getType().toString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
org.eclipse.jdt.core.dom.ASTNode parent = node.getParent();
|
||||||
|
if (parent instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
||||||
|
org.eclipse.jdt.core.dom.Expression caller = mi.getExpression();
|
||||||
|
if (caller instanceof org.eclipse.jdt.core.dom.SimpleName callerSn) {
|
||||||
|
String callerName = callerSn.getIdentifier();
|
||||||
|
if (!callerName.equals(cleanFieldName)) {
|
||||||
|
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
||||||
|
if (callerType != null && callerType.contains("<")) {
|
||||||
|
int start = callerType.indexOf('<');
|
||||||
|
int end = callerType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
foundType[0] = callerType.substring(start + 1, end);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (caller instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
|
||||||
|
String callerName = fa.getName().getIdentifier();
|
||||||
|
if (!callerName.equals(cleanFieldName)) {
|
||||||
|
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
||||||
|
if (callerType != null && callerType.contains("<")) {
|
||||||
|
int start = callerType.indexOf('<');
|
||||||
|
int end = callerType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
foundType[0] = callerType.substring(start + 1, end);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return foundType[0];
|
if (foundType[0] != null) return foundType[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to fields
|
||||||
|
for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
return fd.getType().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -618,10 +676,28 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
|
|
||||||
// 2. Inline Instantiation Getter (e.g. new Event(CONSTANT).getType())
|
// 2. Inline Instantiation Getter (e.g. new Event(CONSTANT).getType())
|
||||||
if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||||
|
if (cic.getAnonymousClassDeclaration() != null) {
|
||||||
|
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
||||||
|
if (declObj instanceof org.eclipse.jdt.core.dom.MethodDeclaration mdecl) {
|
||||||
|
if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) {
|
||||||
|
mdecl.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) {
|
||||||
|
if (rs.getExpression() != null) {
|
||||||
|
extractConstantsFromExpression(rs.getExpression(), constants);
|
||||||
|
}
|
||||||
|
return super.visit(rs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for (Object argObj : cic.arguments()) {
|
for (Object argObj : cic.arguments()) {
|
||||||
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Builder Pattern (e.g. MessageBuilder.withPayload(...).setHeader("event", CONSTANT).build())
|
// 3. Builder Pattern (e.g. MessageBuilder.withPayload(...).setHeader("event", CONSTANT).build())
|
||||||
org.eclipse.jdt.core.dom.Expression current = mi;
|
org.eclipse.jdt.core.dom.Expression current = mi;
|
||||||
|
|||||||
@@ -2253,4 +2253,77 @@ class HeuristicCallGraphEngineTest {
|
|||||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_FIELD");
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_FIELD");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromLambda() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import java.util.List;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleList(List<LambdaEvent> events) {
|
||||||
|
events.forEach(e -> service.process(e.getType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LambdaEvent {
|
||||||
|
private String type;
|
||||||
|
public LambdaEvent(String typeArg) { this.type = typeArg; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_lambda");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleList").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
System.out.println("DEBUG LAMBDA CHAINS: " + (chains.isEmpty() ? "empty" : chains.get(0).getTriggerPoint().getPolymorphicEvents()));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).isNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromAnonymousClass() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new EventProvider() {
|
||||||
|
@Override
|
||||||
|
public String getEvent() { return "ANONYMOUS_EVENT"; }
|
||||||
|
}.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventProvider {
|
||||||
|
String getEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_anonymous");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ANONYMOUS_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user