From 60c666f7a1f4052039b95578ec9ffe0aecbca6ec Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sat, 27 Jun 2026 15:10:36 +0200 Subject: [PATCH] better tests --- .../service/AbstractCallGraphEngine.java | 17 ++++- .../analysis/service/ConstantExtractor.java | 15 +++- .../analysis/service/VariableTracer.java | 14 +--- .../analysis/service/NewBugTest.java | 69 +++++++++++++++++++ 4 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/NewBugTest.java diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java index 2d8f18d..b5fd279 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -352,7 +352,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { resolvedValue = cic.toString() + "." + methodName + "()"; } } - if (!polymorphicEvents.isEmpty()) { + System.out.println("Returning early with events: " + polymorphicEvents + " for " + resolvedValue); if (!polymorphicEvents.isEmpty()) { return TriggerPoint.builder() .event(resolvedValue) .className(tp.getClassName()) @@ -438,6 +438,21 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { if (values != null && !values.isEmpty()) { polymorphicEvents.addAll(values); } + if (polymorphicEvents.isEmpty() && variableTracer != null) { + String initializer = variableTracer.traceLocalVariable(methodFqn, varName); + if (initializer != null && !initializer.equals(varName)) { + ASTParser mapParser = ASTParser.newParser(AST.JLS17); + mapParser.setKind(ASTParser.K_EXPRESSION); + mapParser.setSource(initializer.toCharArray()); + ASTNode mapNode = mapParser.createAST(null); + if (mapNode instanceof Expression) { + List mapTraced = variableTracer.traceVariableAll((Expression) mapNode); + for (Expression t : mapTraced) { + constantExtractor.extractConstantsFromExpression(t, polymorphicEvents); + } + } + } + } sourceMethod = methodFqn; break; } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java index 5617e46..8a4e09b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java @@ -72,11 +72,20 @@ public class ConstantExtractor { } else if (expr instanceof MethodInvocation mi) { String methodName = mi.getName().getIdentifier(); - if (methodName.equals("get") && mi.arguments().size() == 1 && mi.getExpression() != null) { - extractConstantsFromExpression(mi.getExpression(), constants); + System.out.println("Processing mi: " + mi); if (methodName.equals("get") || methodName.equals("getOrDefault")) { + if (mi.getExpression() != null) { + if (variableTracer != null) { + List traced = variableTracer.traceVariableAll(mi.getExpression()); + for (Expression t : traced) { + extractConstantsFromExpression(t, constants); + } + } else { + extractConstantsFromExpression(mi.getExpression(), constants); + } + } return; } - if ((methodName.equals("of") || methodName.equals("asList")) && mi.getExpression() instanceof SimpleName) { + if (methodName.equals("of") || methodName.equals("ofEntries") || methodName.equals("asList") || methodName.equals("entry")) { for (Object argObj : mi.arguments()) { extractConstantsFromExpression((Expression) argObj, constants); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/VariableTracer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/VariableTracer.java index 6ee3fe2..72762da 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/VariableTracer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/VariableTracer.java @@ -341,19 +341,7 @@ public class VariableTracer { Expression traced = traceVariable(expr); if (traced instanceof MethodInvocation mi) { Expression innerMost = unwrapMethodInvocation(mi, 0); - if (innerMost instanceof MethodInvocation innerMi) { - if (innerMi.getExpression() instanceof SimpleName sn) { - stringified.add(sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()"); - } else { - stringified.add(innerMi.getName().getIdentifier() + "()"); - } - } else if (innerMost instanceof SimpleName sn) { - stringified.add(sn.getIdentifier()); - } else { - stringified.add(innerMost.toString()); - } - } else if (traced instanceof SimpleName sn) { - stringified.add(sn.getIdentifier()); + stringified.add(innerMost.toString()); } else if (traced instanceof ArrayInitializer) { stringified.add("new Object[]" + traced.toString()); } else { diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/NewBugTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/NewBugTest.java new file mode 100644 index 0000000..991891c --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/NewBugTest.java @@ -0,0 +1,69 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallChain; +import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NewBugTest { + + @Test + void shouldResolveMapLookup() throws IOException { + String source = """ + package com.example; + import java.util.Map; + + public class Controller { + private StateMachine machine; + private static final Map MAP = Map.of( + InputEnum.IN_A, TransitionEnum.STATE_X, + InputEnum.IN_B, TransitionEnum.STATE_Y + ); + + public void process(InputEnum input) { + TransitionEnum trans = MAP.get(input); + machine.fire(trans); + } + } + + class StateMachine { + public void fire(TransitionEnum event) {} + } + + enum InputEnum { IN_A, IN_B } + enum TransitionEnum { STATE_X, STATE_Y } + """; + + Path tempDir = Files.createTempDirectory("callgraph_test_map"); + Files.writeString(tempDir.resolve("Config.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + + HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context); + + EntryPoint entryPoint = EntryPoint.builder() + .className("com.example.Controller") + .methodName("process") + .build(); + + TriggerPoint trigger = TriggerPoint.builder() + .className("com.example.StateMachine") + .methodName("fire") + .event("event") + .build(); + + List chains = engine.findChains(List.of(entryPoint), List.of(trigger)); + assertThat(chains).hasSize(1); + System.out.println("Poly events: " + chains.get(0).getTriggerPoint().getPolymorphicEvents()); + assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("TransitionEnum.STATE_X", "TransitionEnum.STATE_Y"); + } +}