heuristics updated enum collision
This commit is contained in:
@@ -310,6 +310,11 @@ public class ConstantResolver {
|
|||||||
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues);
|
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues);
|
||||||
if (switchVar == null) return null;
|
if (switchVar == null) return null;
|
||||||
|
|
||||||
|
String switchType = null;
|
||||||
|
if (switchVar.contains(".")) {
|
||||||
|
switchType = switchVar.substring(0, switchVar.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
boolean matchingCase = false;
|
boolean matchingCase = false;
|
||||||
for (Object stmtObj : ss.statements()) {
|
for (Object stmtObj : ss.statements()) {
|
||||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
|
if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
|
||||||
@@ -322,10 +327,20 @@ public class ConstantResolver {
|
|||||||
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
|
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
|
||||||
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
||||||
caseVal = caseSn.getIdentifier();
|
caseVal = caseSn.getIdentifier();
|
||||||
|
if (switchType != null) {
|
||||||
|
caseVal = switchType + "." + caseVal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (caseVal != null && switchVar.endsWith(caseVal)) {
|
if (caseVal != null) {
|
||||||
matchingCase = true;
|
if (switchVar.contains(".") && caseVal.contains(".")) {
|
||||||
break;
|
if (switchVar.equals(caseVal)) {
|
||||||
|
matchingCase = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (switchVar.endsWith(caseVal)) {
|
||||||
|
matchingCase = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,6 +361,11 @@ public class ConstantResolver {
|
|||||||
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues);
|
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues);
|
||||||
if (switchVar == null) return null;
|
if (switchVar == null) return null;
|
||||||
|
|
||||||
|
String switchType = null;
|
||||||
|
if (switchVar.contains(".")) {
|
||||||
|
switchType = switchVar.substring(0, switchVar.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
boolean matchingCase = false;
|
boolean matchingCase = false;
|
||||||
for (Object stmtObj : se.statements()) {
|
for (Object stmtObj : se.statements()) {
|
||||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
|
if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
|
||||||
@@ -358,10 +378,20 @@ public class ConstantResolver {
|
|||||||
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
|
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
|
||||||
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
||||||
caseVal = caseSn.getIdentifier();
|
caseVal = caseSn.getIdentifier();
|
||||||
|
if (switchType != null) {
|
||||||
|
caseVal = switchType + "." + caseVal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (caseVal != null && switchVar.endsWith(caseVal)) {
|
if (caseVal != null) {
|
||||||
matchingCase = true;
|
if (switchVar.contains(".") && caseVal.contains(".")) {
|
||||||
break;
|
if (switchVar.equals(caseVal)) {
|
||||||
|
matchingCase = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (switchVar.endsWith(caseVal)) {
|
||||||
|
matchingCase = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,23 @@ import java.util.*;
|
|||||||
if (edges != null) {
|
if (edges != null) {
|
||||||
for (CallEdge edge : edges) {
|
for (CallEdge edge : edges) {
|
||||||
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
||||||
|
String expectedType = getParameterType(target, paramIndex);
|
||||||
|
if (expectedType != null && paramIndex < edge.getArguments().size()) {
|
||||||
|
String argValue = edge.getArguments().get(paramIndex);
|
||||||
|
String actualType = null;
|
||||||
|
if (argValue.contains(".") && !argValue.contains("(")) {
|
||||||
|
String prefix = argValue.substring(0, argValue.lastIndexOf('.'));
|
||||||
|
if (!prefix.equals("this") && !prefix.equals("super")) {
|
||||||
|
actualType = prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (actualType == null && !argValue.contains(".")) {
|
||||||
|
actualType = getVariableDeclaredType(caller, argValue);
|
||||||
|
}
|
||||||
|
if (actualType != null && !isTypeCompatible(actualType, expectedType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (paramIndex < edge.getArguments().size()) {
|
if (paramIndex < edge.getArguments().size()) {
|
||||||
String arg = edge.getArguments().get(paramIndex);
|
String arg = edge.getArguments().get(paramIndex);
|
||||||
if (arg != null) {
|
if (arg != null) {
|
||||||
@@ -150,7 +167,6 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
@@ -181,12 +197,10 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse resolvedValue using JDT to robustly handle complex expressions
|
// Parse resolvedValue using JDT to robustly handle complex expressions
|
||||||
System.out.println("DEBUG Parsing resolvedValue: " + resolvedValue);
|
|
||||||
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.JLS17);
|
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.JLS17);
|
||||||
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
||||||
exprParser.setSource(resolvedValue.toCharArray());
|
exprParser.setSource(resolvedValue.toCharArray());
|
||||||
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
|
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
|
||||||
System.out.println("DEBUG exprNode: " + (exprNode != null ? exprNode.getClass().getName() : "null") + ", node: " + exprNode);
|
|
||||||
|
|
||||||
String varName = null;
|
String varName = null;
|
||||||
String methodName = null;
|
String methodName = null;
|
||||||
@@ -530,18 +544,50 @@ import java.util.*;
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getParameterType(String methodFqn, int index) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
||||||
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
|
if (md != null && index < md.parameters().size()) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(index);
|
||||||
|
return svd.getType().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isTypeCompatible(String actualType, String expectedType) {
|
||||||
|
if (actualType == null || expectedType == null) return true;
|
||||||
|
if (actualType.equals(expectedType)) return true;
|
||||||
|
if (actualType.endsWith("." + expectedType) || expectedType.endsWith("." + actualType)) return true;
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(actualType);
|
||||||
|
if (td != null) {
|
||||||
|
String superFqn = context.getSuperclassFqn(td);
|
||||||
|
while (superFqn != null) {
|
||||||
|
if (superFqn.equals(expectedType) || superFqn.endsWith("." + expectedType)) return true;
|
||||||
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
|
superFqn = superTd != null ? context.getSuperclassFqn(superTd) : null;
|
||||||
|
}
|
||||||
|
for (Object interfaceType : td.superInterfaceTypes()) {
|
||||||
|
if (interfaceType.toString().equals(expectedType) || interfaceType.toString().endsWith("." + expectedType)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
|
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
|
||||||
System.out.println("DEBUG getVariableDeclaredType called with: " + methodFqn + ", " + cleanFieldName);
|
|
||||||
if (methodFqn == null) return null;
|
if (methodFqn == null) return null;
|
||||||
int lastDot = methodFqn.lastIndexOf('.');
|
int lastDot = methodFqn.lastIndexOf('.');
|
||||||
if (lastDot > 0) {
|
if (lastDot > 0) {
|
||||||
String fqn = methodFqn.substring(0, lastDot);
|
String fqn = methodFqn.substring(0, lastDot);
|
||||||
String methodName = methodFqn.substring(lastDot + 1);
|
String methodName = methodFqn.substring(lastDot + 1);
|
||||||
TypeDeclaration td = context.getTypeDeclaration(fqn);
|
TypeDeclaration td = context.getTypeDeclaration(fqn);
|
||||||
System.out.println("DEBUG getTypeDeclaration for " + fqn + " = " + (td != null));
|
|
||||||
if (td != null) {
|
if (td != null) {
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, false);
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, false);
|
||||||
System.out.println("DEBUG findMethodDeclaration for " + methodName + " = " + (md != null));
|
|
||||||
if (md != null) {
|
if (md != null) {
|
||||||
for (Object pObj : md.parameters()) {
|
for (Object pObj : md.parameters()) {
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
||||||
@@ -551,7 +597,6 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
final String[] foundType = new String[1];
|
final String[] foundType = new String[1];
|
||||||
if (md.getBody() != null) {
|
if (md.getBody() != null) {
|
||||||
System.out.println("DEBUG visiting body for " + cleanFieldName);
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
md.getBody().accept(new ASTVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(VariableDeclarationStatement node) {
|
public boolean visit(VariableDeclarationStatement node) {
|
||||||
@@ -568,12 +613,9 @@ import java.util.*;
|
|||||||
public boolean visit(org.eclipse.jdt.core.dom.LambdaExpression node) {
|
public boolean visit(org.eclipse.jdt.core.dom.LambdaExpression node) {
|
||||||
for (Object paramObj : node.parameters()) {
|
for (Object paramObj : node.parameters()) {
|
||||||
org.eclipse.jdt.core.dom.VariableDeclaration param = (org.eclipse.jdt.core.dom.VariableDeclaration) paramObj;
|
org.eclipse.jdt.core.dom.VariableDeclaration param = (org.eclipse.jdt.core.dom.VariableDeclaration) paramObj;
|
||||||
System.out.println("DEBUG visiting lambda param: " + param.getName().getIdentifier());
|
|
||||||
if (param.getName().getIdentifier().equals(cleanFieldName)) {
|
if (param.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
System.out.println("DEBUG found lambda param: " + cleanFieldName);
|
|
||||||
if (param instanceof SingleVariableDeclaration svd && svd.getType() != null) {
|
if (param instanceof SingleVariableDeclaration svd && svd.getType() != null) {
|
||||||
foundType[0] = svd.getType().toString();
|
foundType[0] = svd.getType().toString();
|
||||||
System.out.println("DEBUG lambda explicit type: " + foundType[0]);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1903,7 +1945,15 @@ import java.util.*;
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isHeuristicMatch(String neighbor, String target) {
|
protected boolean isHeuristicMatch(String neighbor, String target) {
|
||||||
if (target.endsWith("." + neighbor)) return true;
|
if (neighbor == null || target == null) return false;
|
||||||
|
if (neighbor.equals(target)) return true;
|
||||||
|
|
||||||
|
// If both have packages/classes, they must match more strictly
|
||||||
|
if (neighbor.contains(".") && target.contains(".")) {
|
||||||
|
return neighbor.endsWith(target) || target.endsWith(neighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for simple names (only if one side is unqualified)
|
||||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||||
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
||||||
return simpleNeighbor.equals(simpleTarget);
|
return simpleNeighbor.equals(simpleTarget);
|
||||||
|
|||||||
@@ -582,10 +582,27 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
if (edges != null) {
|
if (edges != null) {
|
||||||
for (CallEdge edge : edges) {
|
for (CallEdge edge : edges) {
|
||||||
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
||||||
|
String expectedType = getParameterType(target, paramIndex);
|
||||||
|
if (expectedType != null && paramIndex < edge.getArguments().size()) {
|
||||||
|
String argValue = edge.getArguments().get(paramIndex);
|
||||||
|
String actualType = null;
|
||||||
|
if (argValue.contains(".") && !argValue.contains("(")) {
|
||||||
|
String prefix = argValue.substring(0, argValue.lastIndexOf('.'));
|
||||||
|
if (!prefix.equals("this") && !prefix.equals("super")) {
|
||||||
|
actualType = prefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (actualType == null && !argValue.contains(".")) {
|
||||||
|
actualType = getVariableDeclaredType(caller, argValue);
|
||||||
|
}
|
||||||
|
if (actualType != null && !isTypeCompatible(actualType, expectedType)) {
|
||||||
|
log.debug("Call rejected due to type mismatch: {} calling {} with type {} instead of expected {}", caller, edge.getTargetMethod(), actualType, expectedType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (paramIndex < edge.getArguments().size()) {
|
if (paramIndex < edge.getArguments().size()) {
|
||||||
String arg = edge.getArguments().get(paramIndex);
|
String arg = edge.getArguments().get(paramIndex);
|
||||||
if (arg != null) {
|
if (arg != null) {
|
||||||
System.out.println("DEBUG TRACE: caller=" + caller + " target=" + target + " paramIndex=" + paramIndex + " arg=" + arg + " receiver=" + edge.getReceiver());
|
|
||||||
String receiver = edge.getReceiver();
|
String receiver = edge.getReceiver();
|
||||||
if (receiver != null && !receiver.isEmpty() && ("this".equals(arg) || arg.startsWith("this.") || "super".equals(arg) || arg.startsWith("super."))) {
|
if (receiver != null && !receiver.isEmpty() && ("this".equals(arg) || arg.startsWith("this.") || "super".equals(arg) || arg.startsWith("super."))) {
|
||||||
String actualReceiver = receiver;
|
String actualReceiver = receiver;
|
||||||
@@ -738,7 +755,6 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<String> evaluateGetterOnReceiver(String resolvedValue, String methodSuffix, String entryMethod, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
private List<String> evaluateGetterOnReceiver(String resolvedValue, String methodSuffix, String entryMethod, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: resolvedValue=" + resolvedValue + " methodSuffix=" + methodSuffix + " entryMethod=" + entryMethod);
|
|
||||||
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.JLS17);
|
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.JLS17);
|
||||||
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
||||||
exprParser.setSource(resolvedValue.toCharArray());
|
exprParser.setSource(resolvedValue.toCharArray());
|
||||||
@@ -753,13 +769,11 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
if (receiver instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
if (receiver instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
||||||
receiverName = sn.getIdentifier();
|
receiverName = sn.getIdentifier();
|
||||||
receiverType = getVariableDeclaredType(entryMethod, receiverName);
|
receiverType = getVariableDeclaredType(entryMethod, receiverName);
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: receiverName=" + receiverName + " receiverType=" + receiverType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receiverType == null && path.size() > 1) {
|
if (receiverType == null && path.size() > 1) {
|
||||||
String callerMethod = path.get(1);
|
String callerMethod = path.get(1);
|
||||||
receiverType = getVariableDeclaredType(callerMethod, receiverName);
|
receiverType = getVariableDeclaredType(callerMethod, receiverName);
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: fallback receiverType=" + receiverType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receiverType != null) {
|
if (receiverType != null) {
|
||||||
@@ -767,7 +781,6 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
if (receiverType.contains("<")) {
|
if (receiverType.contains("<")) {
|
||||||
simpleReceiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
simpleReceiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||||
}
|
}
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: simpleReceiverType=" + simpleReceiverType);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType);
|
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType);
|
||||||
if (td != null) {
|
if (td != null) {
|
||||||
MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true);
|
MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true);
|
||||||
@@ -775,10 +788,8 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
Map<String, String> fieldValues = new HashMap<>();
|
Map<String, String> fieldValues = new HashMap<>();
|
||||||
String varName = receiverName;
|
String varName = receiverName;
|
||||||
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: initExpr=" + initExpr + " class=" + (initExpr != null ? initExpr.getClass().getName() : "null"));
|
|
||||||
if (initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
if (initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||||
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: fieldValues=" + fieldValues);
|
|
||||||
int lastDot = entryMethod.lastIndexOf('.');
|
int lastDot = entryMethod.lastIndexOf('.');
|
||||||
if (lastDot > 0) {
|
if (lastDot > 0) {
|
||||||
String className = entryMethod.substring(0, lastDot);
|
String className = entryMethod.substring(0, lastDot);
|
||||||
@@ -793,7 +804,6 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String result = constantResolver.evaluateMethodBodyWithLocals(getter, fieldValues, context, new HashSet<>());
|
String result = constantResolver.evaluateMethodBodyWithLocals(getter, fieldValues, context, new HashSet<>());
|
||||||
System.out.println("DEBUG evaluateGetterOnReceiver: result=" + result);
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
String returnType = getter.getReturnType2() != null ? getter.getReturnType2().toString() : null;
|
String returnType = getter.getReturnType2() != null ? getter.getReturnType2().toString() : null;
|
||||||
if (returnType != null && !result.contains(".")) {
|
if (returnType != null && !result.contains(".")) {
|
||||||
|
|||||||
@@ -227,4 +227,89 @@ class HeuristicCallGraphEngineExtendedTest {
|
|||||||
.containsExactlyInAnyOrder("TransitionEnum.CHECKOUT_STARTED");
|
.containsExactlyInAnyOrder("TransitionEnum.CHECKOUT_STARTED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveCorrectEnumWhenNamesCollide() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class OrderService {
|
||||||
|
private StateMachine machine;
|
||||||
|
|
||||||
|
public void processOrder() {
|
||||||
|
// We use OrderEvent.PROCESS
|
||||||
|
machine.fire(OrderEvent.PROCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InventoryService {
|
||||||
|
private StateMachine machine;
|
||||||
|
|
||||||
|
public void updateInventory() {
|
||||||
|
// We use InternalEvent.PROCESS which has same simple name
|
||||||
|
machine.fireInternal(InternalEvent.PROCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void wrongProcess() {
|
||||||
|
// This calls fire(OrderEvent) but with InternalEvent.PROCESS
|
||||||
|
// It should be REJECTED by type-aware heuristic matching if it matches fire(OrderEvent)
|
||||||
|
machine.fire(InternalEvent.PROCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
// Two overloaded methods or methods in different classes (simulated by names)
|
||||||
|
public void fire(OrderEvent event) {}
|
||||||
|
public void fireInternal(InternalEvent event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvent { PROCESS, CANCEL }
|
||||||
|
enum InternalEvent { PROCESS, AUDIT }
|
||||||
|
""";
|
||||||
|
|
||||||
|
java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("callgraph_test_collision");
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("CollisionConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
// Scenario 1: Trace OrderService.processOrder
|
||||||
|
EntryPoint entryPoint1 = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("processOrder")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger1 = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("fire")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains1 = engine.findChains(List.of(entryPoint1), List.of(trigger1));
|
||||||
|
|
||||||
|
assertThat(chains1).hasSize(1);
|
||||||
|
assertThat(chains1.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactly("OrderEvent.PROCESS")
|
||||||
|
.doesNotContain("InternalEvent.PROCESS");
|
||||||
|
|
||||||
|
// Scenario 2: Trace InventoryService.updateInventory
|
||||||
|
EntryPoint entryPoint2 = EntryPoint.builder()
|
||||||
|
.className("com.example.InventoryService")
|
||||||
|
.methodName("updateInventory")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger2 = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("fireInternal")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains2 = engine.findChains(List.of(entryPoint2), List.of(trigger2));
|
||||||
|
|
||||||
|
assertThat(chains2).hasSize(1);
|
||||||
|
assertThat(chains2.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactly("InternalEvent.PROCESS")
|
||||||
|
.doesNotContain("OrderEvent.PROCESS");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user