From 2e83af0f33ad89e6a47582e5a6379937fd3d4099 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sat, 20 Jun 2026 06:51:15 +0200 Subject: [PATCH] fix attempt 1 --- TODO.md | 3 +- .../enricher/TransitionLinkerEnricher.java | 2 +- .../analysis/service/CallGraphBuilder.java | 8 +- .../TransitionLinkerEnricherTest.java | 24 ++++ .../service/CallGraphBuilderTest.java | 127 ++++++++++++++++++ 5 files changed, 158 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index c620abf..73a3186 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,9 @@ 1.extract from all possible formats (ask chatgpt) -[DONE] json [TODO] xml, maybe yaml 2. [PLAN] Support external dependencies (JARs/compiled classes): - Enable binding resolution in ASTParser (`setResolveBindings(true)`). - Configure ASTParser with project classpath (including JARs). - Refactor `CodebaseContext` to handle resolved bindings, not just local AST nodes. - - Implement tests using external libraries as base classes. \ No newline at end of file + - Implement tests using external libraries as base classes. diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java index f9f891b..2466d50 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java @@ -44,7 +44,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { boolean isWildcard = triggerEvent.equals("event") || triggerEvent.equals("e") || triggerEvent.equals("msg") || triggerEvent.equals("message") || - triggerEvent.equals("payload") || triggerEvent.matches(".*\\.get[A-Z].*\\(\\)"); + triggerEvent.equals("payload") || triggerEvent.matches(".*\\.getType\\(\\)"); if (isWildcard) { String targetVar = chain.getContextMachineId(); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java index cebfef2..2a9cea8 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java @@ -541,9 +541,11 @@ public class CallGraphBuilder { expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor } - if (expr instanceof ClassInstanceCreation cic) { - if (!cic.arguments().isEmpty()) { - expr = (Expression) cic.arguments().get(0); + if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) { + Expression firstArg = (Expression) cic.arguments().get(0); + String resolved = constantResolver.resolve(firstArg, context); + if (resolved != null) { + expr = firstArg; // Only unwrap if it's actually a constant } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java index d3600af..cca517c 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java @@ -167,6 +167,30 @@ class TransitionLinkerEnricherTest { assertThat(updatedChain.getMatchedTransitions()).hasSize(1); } + @Test + void shouldNotMatchArbitraryGetXMethodWildcard() { + Transition t1 = new Transition(); + t1.setSourceStates(List.of(State.of("NEW", "NEW"))); + t1.setTargetStates(List.of(State.of("PAID", "PAID"))); + t1.setEvent(Event.of("PAY", "PAY")); + + CallChain chain = CallChain.builder() + .triggerPoint(TriggerPoint.builder().event("richEvent.getId()").build()) + .contextMachineId("testMachine") + .build(); + + AnalysisResult result = AnalysisResult.builder() + .name("testMachine") + .transitions(List.of(t1)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(result, null, null); + + CallChain updatedChain = result.getMetadata().getCallChains().get(0); + assertThat(updatedChain.getMatchedTransitions()).isNull(); + } + @Test void shouldAllowWildcardWhenContextMachineIdIsMissing() { Transition t1 = new Transition(); diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java index 9db6d32..35b55a8 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java @@ -954,4 +954,131 @@ class CallGraphBuilderTest { // It returns the array access expression as a string. assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("events[0]"); } + + @Test + void shouldProperlyUnwrapDeeplyNestedWrapperCallsForFix1() throws IOException { + String source = """ + package com.example; + public class OrderController { + private OrderService service; + public void processOrderEvent(MyEvent event) { + OrderEvents eventType = wrap1(wrap2(wrap3(event.getType()))); + service.updateOrderState(eventType); + } + + private OrderEvents wrap1(OrderEvents e) { return e; } + private OrderEvents wrap2(OrderEvents e) { return e; } + private OrderEvents wrap3(OrderEvents e) { return e; } + } + + class OrderService { + public void updateOrderState(OrderEvents event) { } + } + + class MyEvent { + public OrderEvents getType() { return OrderEvents.CREATE; } + } + enum OrderEvents { CREATE } + """; + Path tempDir = Files.createTempDirectory("callgraph_nested_wrap"); + Files.writeString(tempDir.resolve("OrderConfig.java"), source); + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + CallGraphBuilder builder = new CallGraphBuilder(context); + + EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("processOrderEvent").build(); + TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("updateOrderState").event("event").build(); + List chains = builder.findChains(List.of(entryPoint), List.of(trigger)); + assertThat(chains).hasSize(1); + assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("OrderEvents.CREATE"); + } + + @Test + void shouldNotExtractClassInstanceCreationEvenWithComplexArgsForFix2() throws IOException { + String source = """ + package com.example; + public class OrderController { + private OrderService service; + public void createOrder(String oID) { + service.processOrderEvent(new OrderCancelledEvent(new OrderId(oID))); + } + } + class OrderService { + public void processOrderEvent(OrderCancelledEvent event) { } + } + class OrderCancelledEvent { + public OrderCancelledEvent(OrderId id) {} + } + class OrderId { + public OrderId(String id) {} + } + """; + Path tempDir = Files.createTempDirectory("callgraph_complex_cic"); + Files.writeString(tempDir.resolve("OrderConfig.java"), source); + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + CallGraphBuilder builder = new CallGraphBuilder(context); + + EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("createOrder").build(); + TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("processOrderEvent").event("event").build(); + List chains = builder.findChains(List.of(entryPoint), List.of(trigger)); + assertThat(chains).hasSize(1); + assertThat(chains.get(0).getTriggerPoint().getEvent()).isEqualTo("new OrderCancelledEvent(new OrderId(oID))"); + } + + @Test + void shouldResolveMethodReturnConstantFromMultiplePackagesForFix3() throws IOException { + String source1 = """ + package com.example.api; + public interface RichEvent { + String getType(); + } + """; + String source2 = """ + package com.example.impl.a; + import com.example.api.RichEvent; + public class EventA implements RichEvent { + public String getType() { return "EVENT_A"; } + } + """; + String source3 = """ + package com.example.impl.b; + import com.example.api.RichEvent; + public class EventB implements RichEvent { + public String getType() { return "EVENT_B"; } + } + """; + String source4 = """ + package com.example.controller; + import com.example.api.RichEvent; + public class EventController { + private com.example.service.EventService service; + public void handleEvent(RichEvent event) { + service.process(event.getType()); + } + } + """; + String source5 = """ + package com.example.service; + public class EventService { + public void process(String type) {} + } + """; + Path tempDir = Files.createTempDirectory("callgraph_multi_pkg"); + Files.writeString(tempDir.resolve("RichEvent.java"), source1); + Files.writeString(tempDir.resolve("EventA.java"), source2); + Files.writeString(tempDir.resolve("EventB.java"), source3); + Files.writeString(tempDir.resolve("EventController.java"), source4); + Files.writeString(tempDir.resolve("EventService.java"), source5); + + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + CallGraphBuilder builder = new CallGraphBuilder(context); + + EntryPoint entryPoint = EntryPoint.builder().className("com.example.controller.EventController").methodName("handleEvent").build(); + TriggerPoint trigger = TriggerPoint.builder().className("com.example.service.EventService").methodName("process").event("type").build(); + List chains = builder.findChains(List.of(entryPoint), List.of(trigger)); + assertThat(chains).hasSize(1); + assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_A", "EVENT_B"); + } }