edge case fixes
This commit is contained in:
@@ -261,6 +261,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (varName != null && declaredType == null) {
|
||||
for (String methodFqn : path) {
|
||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||
System.out.println("DEBUG getVariableDeclaredType(" + methodFqn + ", " + varName + ") = " + declaredType);
|
||||
if (declaredType != null) {
|
||||
sourceMethod = methodFqn;
|
||||
break;
|
||||
@@ -383,11 +384,12 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td != null) {
|
||||
String cleanFieldName = varName.startsWith("this.") ? varName.substring(5) : varName;
|
||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||
if (md != null) {
|
||||
for (Object pObj : md.parameters()) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
||||
if (svd.getName().getIdentifier().equals(varName)) {
|
||||
if (svd.getName().getIdentifier().equals(cleanFieldName)) {
|
||||
return svd.getType().toString();
|
||||
}
|
||||
}
|
||||
@@ -398,15 +400,71 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
public boolean visit(VariableDeclarationStatement node) {
|
||||
for (Object fragObj : node.fragments()) {
|
||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||
if (frag.getName().getIdentifier().equals(varName)) {
|
||||
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
|
||||
foundType[0] = node.getType().toString();
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -618,8 +676,26 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
|
||||
// 2. Inline Instantiation Getter (e.g. new Event(CONSTANT).getType())
|
||||
if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||
for (Object argObj : cic.arguments()) {
|
||||
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
||||
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()) {
|
||||
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2253,4 +2253,77 @@ class HeuristicCallGraphEngineTest {
|
||||
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