Fix AST constructor tracking gap for variables and fields
This commit is contained in:
@@ -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];
|
||||||
@@ -696,9 +697,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;
|
||||||
}
|
}
|
||||||
@@ -795,8 +842,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,123 @@ 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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -408,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",
|
||||||
@@ -416,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" : {
|
||||||
|
|||||||
Reference in New Issue
Block a user