Compare commits
3 Commits
c903b079d3
...
32de0a51c6
| Author | SHA1 | Date | |
|---|---|---|---|
| 32de0a51c6 | |||
| cbb00e8a0f | |||
| 6596303b7d |
@@ -144,6 +144,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String tracedVar = traceLocalVariable(entryMethod, currentParamName);
|
String tracedVar = traceLocalVariable(entryMethod, currentParamName);
|
||||||
|
System.out.println("DEBUG tracedVar: " + tracedVar + " for currentParamName: " + currentParamName);
|
||||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
||||||
String[] extractedFinalTraced = extractMethodSuffix(tracedVar, methodSuffix);
|
String[] extractedFinalTraced = extractMethodSuffix(tracedVar, methodSuffix);
|
||||||
tracedVar = extractedFinalTraced[0];
|
tracedVar = extractedFinalTraced[0];
|
||||||
@@ -260,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;
|
||||||
@@ -307,8 +309,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly
|
|
||||||
// for string literals or enum-like constants.
|
|
||||||
boolean hasValidConstant = false;
|
boolean hasValidConstant = false;
|
||||||
for (String ev : polymorphicEvents) {
|
for (String ev : polymorphicEvents) {
|
||||||
String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev;
|
String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev;
|
||||||
@@ -318,36 +320,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasValidConstant && resolvedValue.contains("(")) {
|
// As a fallback, attempt direct AST extraction (e.g. from MessageBuilder chains or inline constructor args)
|
||||||
java.util.List<String> scraped = new java.util.ArrayList<>();
|
if (!hasValidConstant && exprNode instanceof org.eclipse.jdt.core.dom.Expression) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) exprNode, polymorphicEvents);
|
||||||
// Extract "STRING_LITERALS"
|
|
||||||
java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue);
|
|
||||||
while (m1.find()) {
|
|
||||||
scraped.add(m1.group(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract ENUM_LIKE_CONSTANTS
|
|
||||||
java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue);
|
|
||||||
while (m2.find()) {
|
|
||||||
if (!scraped.contains(m2.group(1))) {
|
|
||||||
scraped.add(m2.group(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scraped.isEmpty()) {
|
|
||||||
polymorphicEvents.clear();
|
|
||||||
polymorphicEvents.addAll(scraped);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones
|
// The aggressive regex scraper has been completely removed to avoid false positives.
|
||||||
if (polymorphicEvents.size() > 1) {
|
// We rely on precise AST traversal instead.
|
||||||
polymorphicEvents.removeIf(e -> {
|
|
||||||
String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e;
|
|
||||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
List<String> newPolyEvents = new ArrayList<>();
|
List<String> newPolyEvents = new ArrayList<>();
|
||||||
for (String pe : polymorphicEvents) {
|
for (String pe : polymorphicEvents) {
|
||||||
List<String> resolved = resolveClassConstantReturns(pe, context, null);
|
List<String> resolved = resolveClassConstantReturns(pe, context, null);
|
||||||
@@ -359,6 +339,12 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
polymorphicEvents = newPolyEvents;
|
polymorphicEvents = newPolyEvents;
|
||||||
|
|
||||||
|
// Clean up any remaining invalid AST fallbacks (like 'type', 'event', or unresolved class names)
|
||||||
|
polymorphicEvents.removeIf(e -> {
|
||||||
|
String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e;
|
||||||
|
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
||||||
|
});
|
||||||
|
|
||||||
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
||||||
return TriggerPoint.builder()
|
return TriggerPoint.builder()
|
||||||
.event(resolvedValue)
|
.event(resolvedValue)
|
||||||
@@ -398,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,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;
|
||||||
@@ -631,7 +674,44 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Delegate to method return analysis
|
// 2. Inline Instantiation Getter (e.g. new Event(CONSTANT).getType())
|
||||||
|
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()) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Builder Pattern (e.g. MessageBuilder.withPayload(...).setHeader("event", CONSTANT).build())
|
||||||
|
org.eclipse.jdt.core.dom.Expression current = mi;
|
||||||
|
while (current instanceof org.eclipse.jdt.core.dom.MethodInvocation chainMi) {
|
||||||
|
String chainName = chainMi.getName().getIdentifier();
|
||||||
|
if (chainName.equals("setHeader") && chainMi.arguments().size() == 2) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) chainMi.arguments().get(1), constants);
|
||||||
|
} else if (chainName.equals("event") && chainMi.arguments().size() == 1) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) chainMi.arguments().get(0), constants);
|
||||||
|
}
|
||||||
|
current = chainMi.getExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Delegate to method return analysis
|
||||||
TypeDeclaration td = findEnclosingType(mi);
|
TypeDeclaration td = findEnclosingType(mi);
|
||||||
if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
|
if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
|
||||||
List<String> values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null);
|
List<String> values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null);
|
||||||
@@ -693,9 +773,55 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (expr instanceof SimpleName sn) {
|
if (expr instanceof SimpleName sn) {
|
||||||
return sn.getIdentifier();
|
return sn.getIdentifier();
|
||||||
}
|
}
|
||||||
return expr.toString();
|
return initializer[0].toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If local variable not found, check field assignments
|
||||||
|
String cleanFieldName = varName.startsWith("this.") ? varName.substring(5) : varName;
|
||||||
|
final Expression[] fieldInitializer = new Expression[1];
|
||||||
|
ASTVisitor fieldVisitor = new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationFragment node) {
|
||||||
|
if (node.getName().getIdentifier().equals(cleanFieldName) && node.getInitializer() != null) {
|
||||||
|
fieldInitializer[0] = node.getInitializer();
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
Expression left = node.getLeftHandSide();
|
||||||
|
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(cleanFieldName)) ||
|
||||||
|
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(cleanFieldName))) {
|
||||||
|
fieldInitializer[0] = node.getRightHandSide();
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||||
|
if (frag.getName().getIdentifier().equals(cleanFieldName) && frag.getInitializer() != null) {
|
||||||
|
fieldInitializer[0] = frag.getInitializer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fieldInitializer[0] != null) return fieldInitializer[0].toString();
|
||||||
|
|
||||||
|
for (MethodDeclaration classMd : td.getMethods()) {
|
||||||
|
if (classMd.isConstructor() && classMd.getBody() != null) {
|
||||||
|
classMd.getBody().accept(fieldVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fieldInitializer[0] != null) return fieldInitializer[0].toString();
|
||||||
|
|
||||||
|
for (MethodDeclaration classMd : td.getMethods()) {
|
||||||
|
if (!classMd.isConstructor() && classMd.getBody() != null) {
|
||||||
|
classMd.getBody().accept(fieldVisitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fieldInitializer[0] != null) return fieldInitializer[0].toString();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -792,8 +918,11 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String[] extractMethodSuffix(String paramName, String currentSuffix) {
|
protected String[] extractMethodSuffix(String paramName, String currentSuffix) {
|
||||||
int dotIndex = paramName.indexOf('.');
|
int dotIndex = paramName.lastIndexOf('.');
|
||||||
if (dotIndex > 0 && dotIndex + 1 < paramName.length() && Character.isLowerCase(paramName.charAt(dotIndex + 1))) {
|
if (dotIndex > 0 && dotIndex + 1 < paramName.length() && Character.isLowerCase(paramName.charAt(dotIndex + 1))) {
|
||||||
|
if (paramName.substring(0, dotIndex).equals("this") && !paramName.endsWith("()")) {
|
||||||
|
return new String[] { paramName, currentSuffix };
|
||||||
|
}
|
||||||
return new String[] { paramName.substring(0, dotIndex), paramName.substring(dotIndex) + currentSuffix };
|
return new String[] { paramName.substring(0, dotIndex), paramName.substring(dotIndex) + currentSuffix };
|
||||||
}
|
}
|
||||||
return new String[] { paramName, currentSuffix };
|
return new String[] { paramName, currentSuffix };
|
||||||
|
|||||||
@@ -2134,5 +2134,196 @@ class HeuristicCallGraphEngineTest {
|
|||||||
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("COMPLEX_INLINE_CONST");
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("COMPLEX_INLINE_CONST");
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromConstructorAssignment() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
EventWithConstructorArg e = new EventWithConstructorArg("EVENT_FROM_CONSTRUCTOR", 123);
|
||||||
|
service.process(e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_constructor_assignment");
|
||||||
|
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("EVENT_FROM_CONSTRUCTOR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPrioritizeSetterOverConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
EventWithConstructorArg e = new EventWithConstructorArg("EVENT_FROM_CONSTRUCTOR", 123);
|
||||||
|
e.setType("EVENT_FROM_SETTER");
|
||||||
|
service.process(e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public void setType(String t) { this.type = t; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_setter_priority");
|
||||||
|
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("EVENT_FROM_SETTER");
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromFieldConstructorAssignment() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
private EventWithConstructorArg e;
|
||||||
|
public OrderController() {
|
||||||
|
this.e = new EventWithConstructorArg("EVENT_FROM_FIELD", 123);
|
||||||
|
}
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(this.e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventWithConstructorArg {
|
||||||
|
private String type;
|
||||||
|
public EventWithConstructorArg(String typeArg, int other) {
|
||||||
|
this.type = typeArg;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_field_assignment");
|
||||||
|
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));
|
||||||
|
System.out.println("DEBUG 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()).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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -381,10 +381,18 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 13,
|
"lineNumber" : 13,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvents.PAY", "OrderEvents.CANCEL" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : null
|
"matchedTransitions" : [ {
|
||||||
|
"sourceState" : "OrderStates.SUBMITTED",
|
||||||
|
"targetState" : "OrderStates.PAID",
|
||||||
|
"event" : "OrderEvents.PAY"
|
||||||
|
}, {
|
||||||
|
"sourceState" : "OrderStates.SUBMITTED",
|
||||||
|
"targetState" : "OrderStates.CANCELED",
|
||||||
|
"event" : "OrderEvents.CANCEL"
|
||||||
|
} ]
|
||||||
}, {
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
@@ -400,7 +408,7 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payList", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payList", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "event",
|
"event" : "new PayEvent()",
|
||||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||||
"methodName" : "processEvent",
|
"methodName" : "processEvent",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||||
@@ -408,25 +416,13 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 13,
|
"lineNumber" : 13,
|
||||||
"polymorphicEvents" : null
|
"polymorphicEvents" : [ "OrderEvents.PAY" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
"sourceState" : "OrderStates.SUBMITTED",
|
"sourceState" : "OrderStates.SUBMITTED",
|
||||||
"targetState" : "OrderStates.PAID",
|
"targetState" : "OrderStates.PAID",
|
||||||
"event" : "OrderEvents.PAY"
|
"event" : "OrderEvents.PAY"
|
||||||
}, {
|
|
||||||
"sourceState" : "OrderStates.PAID",
|
|
||||||
"targetState" : "OrderStates.FULFILLED",
|
|
||||||
"event" : "OrderEvents.FULFILL"
|
|
||||||
}, {
|
|
||||||
"sourceState" : "OrderStates.SUBMITTED",
|
|
||||||
"targetState" : "OrderStates.CANCELED",
|
|
||||||
"event" : "OrderEvents.CANCEL"
|
|
||||||
}, {
|
|
||||||
"sourceState" : "OrderStates.PAID",
|
|
||||||
"targetState" : "OrderStates.CANCELED",
|
|
||||||
"event" : "OrderEvents.ABCD"
|
|
||||||
} ]
|
} ]
|
||||||
}, {
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 25,
|
"lineNumber" : 25,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.PROCESS" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 25,
|
"lineNumber" : 25,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.COMPLETE" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 25,
|
"lineNumber" : 25,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.CANCEL" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 25,
|
"lineNumber" : 25,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.COMPLETE" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 78,
|
"lineNumber" : 78,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.PROCESS" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 25,
|
"lineNumber" : 25,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.PROCESS" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
@@ -346,7 +346,7 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 50,
|
"lineNumber" : 50,
|
||||||
"polymorphicEvents" : [ ]
|
"polymorphicEvents" : [ "OrderEvent.CANCEL" ]
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : [ {
|
"matchedTransitions" : [ {
|
||||||
|
|||||||
Reference in New Issue
Block a user