more bugs 2
This commit is contained in:
@@ -153,9 +153,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
if ("this".equals(arg) || arg.startsWith("this.")) {
|
if ("this".equals(arg) || arg.startsWith("this.")) {
|
||||||
arg = arg.replaceFirst("^this", actualReceiver);
|
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);
|
String[] extractedArg = extractMethodSuffix(arg, methodSuffix);
|
||||||
arg = extractedArg[0];
|
arg = extractedArg[0];
|
||||||
@@ -728,8 +727,18 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
exprParser.setSource(resolvedValue.toCharArray());
|
exprParser.setSource(resolvedValue.toCharArray());
|
||||||
ASTNode exprNode = exprParser.createAST(null);
|
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 getterName = mi.getName().getIdentifier();
|
||||||
|
String propName = methodSuffix.startsWith(".get") ? methodSuffix.substring(4) : methodSuffix.substring(1);
|
||||||
Expression receiver = mi.getExpression();
|
Expression receiver = mi.getExpression();
|
||||||
String receiverName = null;
|
String receiverName = null;
|
||||||
String receiverType = null;
|
String receiverType = null;
|
||||||
@@ -738,7 +747,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
receiverName = sn.getIdentifier();
|
receiverName = sn.getIdentifier();
|
||||||
receiverType = variableTracer.getVariableDeclaredType(entryMethod, receiverName);
|
receiverType = variableTracer.getVariableDeclaredType(entryMethod, receiverName);
|
||||||
} else if (receiver instanceof MethodInvocation receiverMi) {
|
} else if (receiver instanceof MethodInvocation receiverMi) {
|
||||||
String propName = getterName.startsWith("get") ? getterName.substring(3) : getterName;
|
|
||||||
Expression currentMi = receiverMi;
|
Expression currentMi = receiverMi;
|
||||||
while (currentMi instanceof MethodInvocation chainMi) {
|
while (currentMi instanceof MethodInvocation chainMi) {
|
||||||
String mName = chainMi.getName().getIdentifier();
|
String mName = chainMi.getName().getIdentifier();
|
||||||
@@ -754,6 +762,13 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
currentMi = chainMi.getExpression();
|
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) {
|
if (receiverType == null && path.size() > 1) {
|
||||||
|
|||||||
@@ -126,12 +126,12 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
protected String postProcessResolvedArgument(org.eclipse.jdt.core.dom.Expression originalExpr, String resolvedValue) {
|
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
|
// 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
|
// 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.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) {
|
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
|
// Narrow resolution for "localVar.getX()" patterns where localVar is initialized
|
||||||
|
|||||||
@@ -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<CallChain> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user