From 22a0d3f5aa0f0b2fbfe416bfe0df7b6fd231830d Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sat, 27 Jun 2026 15:35:20 +0200 Subject: [PATCH] more bugs 2 --- .../service/AbstractCallGraphEngine.java | 23 +++++- .../service/HeuristicCallGraphEngine.java | 6 +- .../analysis/service/SuperMethodBugTest.java | 75 +++++++++++++++++++ 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/SuperMethodBugTest.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 1329fbc..74f99d7 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 @@ -153,9 +153,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { } if ("this".equals(arg) || arg.startsWith("this.")) { arg = arg.replaceFirst("^this", actualReceiver); - } else if ("super".equals(arg) || arg.startsWith("super.")) { - arg = arg.replaceFirst("^super", actualReceiver); } + // Intentionally not replacing 'super' because super should remain statically bound to the superclass of where the code is defined. } String[] extractedArg = extractMethodSuffix(arg, methodSuffix); arg = extractedArg[0]; @@ -728,8 +727,18 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { exprParser.setSource(resolvedValue.toCharArray()); ASTNode exprNode = exprParser.createAST(null); - if (exprNode instanceof MethodInvocation mi) { + if (exprNode instanceof SuperMethodInvocation smi) { + String getterName = smi.getName().getIdentifier(); + int lastDot = entryMethod.lastIndexOf('.'); + String className = lastDot > 0 ? entryMethod.substring(0, lastDot) : entryMethod; + TypeDeclaration currentTd = context.getTypeDeclaration(className); + if (currentTd != null) { + String receiverType = context.getSuperclassFqn(currentTd); + return constantExtractor.resolveMethodReturnConstant(receiverType, getterName, 0, new HashSet<>(), null); + } + } else if (exprNode instanceof MethodInvocation mi) { String getterName = mi.getName().getIdentifier(); + String propName = methodSuffix.startsWith(".get") ? methodSuffix.substring(4) : methodSuffix.substring(1); Expression receiver = mi.getExpression(); String receiverName = null; String receiverType = null; @@ -738,7 +747,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { receiverName = sn.getIdentifier(); receiverType = variableTracer.getVariableDeclaredType(entryMethod, receiverName); } else if (receiver instanceof MethodInvocation receiverMi) { - String propName = getterName.startsWith("get") ? getterName.substring(3) : getterName; Expression currentMi = receiverMi; while (currentMi instanceof MethodInvocation chainMi) { String mName = chainMi.getName().getIdentifier(); @@ -754,6 +762,13 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { } currentMi = chainMi.getExpression(); } + } else if (receiver instanceof SuperMethodInvocation) { + int lastDot = entryMethod.lastIndexOf('.'); + String className = lastDot > 0 ? entryMethod.substring(0, lastDot) : entryMethod; + TypeDeclaration currentTd = context.getTypeDeclaration(className); + if (currentTd != null) { + receiverType = context.getSuperclassFqn(currentTd); + } } if (receiverType == null && path.size() > 1) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java index f8d2be3..aa0a7fd 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java @@ -126,12 +126,12 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { protected String postProcessResolvedArgument(org.eclipse.jdt.core.dom.Expression originalExpr, String resolvedValue) { // Preserve getter calls on 'this' or 'super' for later resolution in trigger parameter tracing // where we can substitute the actual receiver from the call edge - boolean isThisOrSuperGetter = originalExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi + boolean isThisOrSuperGetter = (originalExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi && (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression || mi.getExpression() instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation) - && mi.arguments().isEmpty(); + && mi.arguments().isEmpty()) || (originalExpr instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation smi && smi.arguments().isEmpty()); if (isThisOrSuperGetter) { - return originalExpr.toString(); // Preserve "this.getTransitionType()" for later substitution + return originalExpr.toString(); // Preserve "this.getTransitionType()" or "super.getEvent()" for later substitution } // Narrow resolution for "localVar.getX()" patterns where localVar is initialized diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/SuperMethodBugTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/SuperMethodBugTest.java new file mode 100644 index 0000000..014e451 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/SuperMethodBugTest.java @@ -0,0 +1,75 @@ +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 SuperMethodBugTest { + + @Test + void shouldResolveSuperMethodCorrectly() throws IOException { + String source = """ + package com.example; + + public class ChildController extends ParentController { + private StateMachine machine; + + @Override + public TransitionEnum getEvent() { + return TransitionEnum.STATE_X; // Child returns X + } + + public void process() { + // Calls super.getEvent() which returns Y, NOT X! + machine.fire(super.getEvent()); + } + } + + class ParentController { + public TransitionEnum getEvent() { + return TransitionEnum.STATE_Y; // Parent returns Y + } + } + + class StateMachine { + public void fire(TransitionEnum event) {} + } + + enum TransitionEnum { STATE_X, STATE_Y } + """; + + Path tempDir = Files.createTempDirectory("callgraph_test_super"); + 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.ChildController") + .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 from super: " + chains.get(0).getTriggerPoint().getPolymorphicEvents()); + // It SHOULD resolve to STATE_Y because we called super.getEvent() + assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactly("TransitionEnum.STATE_Y"); + } +}