up
This commit is contained in:
@@ -121,9 +121,15 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
}
|
||||
|
||||
String expr = constraint;
|
||||
java.util.List<String> allMachines = java.util.List.of("ORDER", "DOCUMENT", "USER");
|
||||
for (String m : allMachines) {
|
||||
boolean isCurrent = m.equals(cleanMachine);
|
||||
java.util.regex.Pattern p = java.util.regex.Pattern.compile("[\"']([a-zA-Z0-9_-]+)[\"']");
|
||||
java.util.regex.Matcher matcher = p.matcher(constraint);
|
||||
java.util.Set<String> literals = new java.util.HashSet<>();
|
||||
while (matcher.find()) {
|
||||
literals.add(matcher.group(1));
|
||||
}
|
||||
|
||||
for (String m : literals) {
|
||||
boolean isCurrent = m.equalsIgnoreCase(cleanMachine);
|
||||
String replacement = isCurrent ? "true" : "false";
|
||||
|
||||
String regex1 = "(?i)[\"']?" + m + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)";
|
||||
@@ -191,7 +197,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
private String simplify(String name) {
|
||||
if (name == null) return null;
|
||||
// Strip common suffixes
|
||||
String simplified = name.replaceAll("(?i)(.*)(Event|Action|Transition|Command)(s)?$", "$1");
|
||||
String simplified = name.replaceAll("(?i)(.+)(Event|Action|Transition|Command)(s)?$", "$1");
|
||||
if (simplified.isEmpty()) {
|
||||
simplified = name;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
|
||||
private String simplify(String name) {
|
||||
if (name == null) return null;
|
||||
String simplified = name.replaceAll("(?i)(.*)(Event|Action|Transition|Command)(s)?$", "$1");
|
||||
String simplified = name.replaceAll("(?i)(.+)(Event|Action|Transition|Command)(s)?$", "$1");
|
||||
if (simplified.isEmpty()) {
|
||||
simplified = name;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import org.eclipse.jdt.core.dom.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class ConstantResolver {
|
||||
@@ -101,19 +104,34 @@ public class ConstantResolver {
|
||||
|
||||
if ("valueOf".equals(mi.getName().getIdentifier())) {
|
||||
String enumFqn = null;
|
||||
Expression arg = null;
|
||||
if (mi.arguments().size() == 2) {
|
||||
enumFqn = resolveEnumFqn((Expression) mi.arguments().get(0), context);
|
||||
arg = (Expression) mi.arguments().get(1);
|
||||
} else if (mi.arguments().size() == 1 && mi.getExpression() != null) {
|
||||
enumFqn = resolveEnumFqn(mi.getExpression(), context);
|
||||
arg = (Expression) mi.arguments().get(0);
|
||||
}
|
||||
|
||||
if (enumFqn != null) {
|
||||
if (arg != null) {
|
||||
String resolvedArg = resolveInternal(arg, context, visited);
|
||||
if (resolvedArg != null && resolvedArg.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
return enumFqn + "." + resolvedArg;
|
||||
}
|
||||
}
|
||||
return "<SYMBOLIC: " + enumFqn + ".*>";
|
||||
}
|
||||
}
|
||||
|
||||
IMethodBinding mb = mi.resolveMethodBinding();
|
||||
if (mb != null) {
|
||||
if (!java.lang.reflect.Modifier.isStatic(mb.getModifiers())) {
|
||||
Expression receiver = mi.getExpression();
|
||||
if (!(receiver instanceof ClassInstanceCreation)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ITypeBinding returnType = mb.getReturnType();
|
||||
if (returnType != null) {
|
||||
java.util.List<String> values = context.getEnumValues(returnType.getQualifiedName());
|
||||
@@ -320,7 +338,43 @@ public class ConstantResolver {
|
||||
|
||||
private String evaluateSwitchStatement(org.eclipse.jdt.core.dom.SwitchStatement ss, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
|
||||
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues);
|
||||
if (switchVar == null) return null;
|
||||
if (switchVar == null) {
|
||||
Set<String> values = new LinkedHashSet<>();
|
||||
for (Object stmtObj : ss.statements()) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) {
|
||||
String val = resolveInternal(rs.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
|
||||
String val = resolveInternal(ys.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es && es.getExpression() != null) {
|
||||
String val = resolveInternal(es.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String switchType = null;
|
||||
if (switchVar.contains(".")) {
|
||||
@@ -340,7 +394,7 @@ public class ConstantResolver {
|
||||
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
||||
caseVal = caseSn.getIdentifier();
|
||||
if (switchType != null) {
|
||||
caseVal = switchType + "." + caseVal;
|
||||
caseVal = switchType + "." + caseVal;
|
||||
}
|
||||
}
|
||||
if (caseVal != null) {
|
||||
@@ -371,7 +425,43 @@ public class ConstantResolver {
|
||||
|
||||
private String evaluateSwitchExpression(org.eclipse.jdt.core.dom.SwitchExpression se, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
|
||||
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues);
|
||||
if (switchVar == null) return null;
|
||||
if (switchVar == null) {
|
||||
Set<String> values = new LinkedHashSet<>();
|
||||
for (Object stmtObj : se.statements()) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
|
||||
String val = resolveInternal(ys.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es && es.getExpression() != null) {
|
||||
String val = resolveInternal(es.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) {
|
||||
String val = resolveInternal(rs.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String switchType = null;
|
||||
if (switchVar.contains(".")) {
|
||||
@@ -657,6 +747,8 @@ public class ConstantResolver {
|
||||
|
||||
private String resolveEnumFqn(Expression expr, CodebaseContext context) {
|
||||
if (expr == null) return null;
|
||||
CompilationUnit contextCu = (expr.getRoot() instanceof CompilationUnit) ? (CompilationUnit) expr.getRoot() : null;
|
||||
|
||||
if (expr instanceof TypeLiteral tl) {
|
||||
ITypeBinding binding = tl.getType().resolveBinding();
|
||||
if (binding != null) {
|
||||
@@ -666,8 +758,8 @@ public class ConstantResolver {
|
||||
if (typeName.endsWith(".class")) {
|
||||
typeName = typeName.substring(0, typeName.length() - 6);
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(typeName);
|
||||
if (td != null) return context.getFqn(td);
|
||||
String resolved = resolveTypeNameToFqn(typeName, contextCu, context);
|
||||
if (resolved != null) return resolved;
|
||||
return typeName;
|
||||
}
|
||||
|
||||
@@ -686,8 +778,47 @@ public class ConstantResolver {
|
||||
if (exprStr.endsWith(".class")) {
|
||||
exprStr = exprStr.substring(0, exprStr.length() - 6);
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(exprStr);
|
||||
if (td != null) return context.getFqn(td);
|
||||
String resolved = resolveTypeNameToFqn(exprStr, contextCu, context);
|
||||
if (resolved != null) return resolved;
|
||||
return exprStr;
|
||||
}
|
||||
|
||||
private String resolveTypeNameToFqn(String name, CompilationUnit contextCu, CodebaseContext context) {
|
||||
if (name == null || name.isEmpty()) return null;
|
||||
|
||||
if (context.getTypeDeclaration(name) != null || context.getEnumValuesMap().containsKey(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (contextCu != null) {
|
||||
for (Object typeObj : contextCu.types()) {
|
||||
if (typeObj instanceof AbstractTypeDeclaration atd) {
|
||||
if (atd.getName().getIdentifier().equals(name)) {
|
||||
String pkg = contextCu.getPackage() != null ? contextCu.getPackage().getName().getFullyQualifiedName() : "";
|
||||
return pkg.isEmpty() ? name : pkg + "." + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Object impObj : contextCu.imports()) {
|
||||
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||
String impName = imp.getName().getFullyQualifiedName();
|
||||
if (!imp.isStatic() && !imp.isOnDemand()) {
|
||||
if (impName.endsWith("." + name)) {
|
||||
return impName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contextCu.getPackage() != null) {
|
||||
String pkg = contextCu.getPackage().getName().getFullyQualifiedName();
|
||||
String pkgFqn = pkg + "." + name;
|
||||
if (context.getEnumValuesMap().containsKey(pkgFqn) || context.getTypeDeclaration(pkgFqn) != null) {
|
||||
return pkgFqn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,17 +54,20 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
boolean foundAny = false;
|
||||
for (TriggerPoint tp : triggers) {
|
||||
String targetMethod = tp.getClassName() + "." + tp.getMethodName();
|
||||
List<String> path = pathFinder.findPath(startMethod, targetMethod, callGraph, new HashSet<>());
|
||||
if (path != null) {
|
||||
List<List<String>> paths = pathFinder.findAllPaths(startMethod, targetMethod, callGraph, new HashSet<>());
|
||||
Set<List<String>> uniquePaths = new LinkedHashSet<>(paths);
|
||||
for (List<String> path : uniquePaths) {
|
||||
foundAny = true;
|
||||
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph);
|
||||
String contextMachineId = pathFinder.extractContextMachineId(path, callGraph);
|
||||
chains.add(CallChain.builder()
|
||||
.entryPoint(ep)
|
||||
.triggerPoint(resolvedTp)
|
||||
.methodChain(path)
|
||||
.contextMachineId(contextMachineId)
|
||||
.build());
|
||||
if (resolvedTp != null) {
|
||||
String contextMachineId = pathFinder.extractContextMachineId(path, callGraph);
|
||||
chains.add(CallChain.builder()
|
||||
.entryPoint(ep)
|
||||
.triggerPoint(resolvedTp)
|
||||
.methodChain(path)
|
||||
.contextMachineId(contextMachineId)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundAny && log.isDebugEnabled()) {
|
||||
@@ -106,10 +109,12 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
protected TriggerPoint resolveTriggerPointParametersOriginal(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph, String[] finalParamNameRef) {
|
||||
String event = tp.getEvent();
|
||||
String currentParamName = event;
|
||||
String resolvedValue = event;
|
||||
String methodSuffix = "";
|
||||
click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel.setCurrentPath(path);
|
||||
try {
|
||||
String event = tp.getEvent();
|
||||
String currentParamName = event;
|
||||
String resolvedValue = event;
|
||||
String methodSuffix = "";
|
||||
|
||||
String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix);
|
||||
currentParamName = extractedEntry[0];
|
||||
@@ -121,15 +126,43 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
String caller = path.get(i - 1);
|
||||
int paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
if (paramIndex < 0) {
|
||||
Map<String, String> parameterValues = variableTracer.buildParameterValuesMap(caller, target, callGraph, path, i);
|
||||
String tracedVar = variableTracer.traceLocalVariable(target, currentParamName, parameterValues);
|
||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
||||
String[] extractedTraced = extractMethodSuffix(tracedVar, methodSuffix);
|
||||
tracedVar = extractedTraced[0];
|
||||
methodSuffix = extractedTraced[1];
|
||||
currentParamName = tracedVar;
|
||||
resolvedValue = tracedVar + methodSuffix;
|
||||
paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
if ("this".equals(currentParamName)) {
|
||||
String actualReceiver = null;
|
||||
for (int j = i; j > 0; j--) {
|
||||
String t = path.get(j);
|
||||
String c = path.get(j - 1);
|
||||
List<CallEdge> edges = callGraph.get(c);
|
||||
if (edges != null) {
|
||||
for (CallEdge edge : edges) {
|
||||
if (edge.getTargetMethod().equals(t) || pathFinder.isHeuristicMatch(edge.getTargetMethod(), t)) {
|
||||
if (edge.getReceiver() != null && !edge.getReceiver().isEmpty()) {
|
||||
actualReceiver = edge.getReceiver();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actualReceiver != null) break;
|
||||
}
|
||||
if (actualReceiver != null) {
|
||||
currentParamName = actualReceiver;
|
||||
resolvedValue = actualReceiver + methodSuffix;
|
||||
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||
paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
}
|
||||
}
|
||||
|
||||
if (paramIndex < 0) {
|
||||
Map<String, String> parameterValues = variableTracer.buildParameterValuesMap(caller, target, callGraph, path, i);
|
||||
String tracedVar = variableTracer.traceLocalVariable(target, currentParamName, parameterValues);
|
||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
||||
String[] extractedTraced = extractMethodSuffix(tracedVar, methodSuffix);
|
||||
tracedVar = extractedTraced[0];
|
||||
methodSuffix = extractedTraced[1];
|
||||
currentParamName = tracedVar;
|
||||
resolvedValue = tracedVar + methodSuffix;
|
||||
paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramIndex < 0) {
|
||||
@@ -230,7 +263,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
boolean hasGetterOnReceiver = methodSuffix.startsWith(".get") && currentParamName != null
|
||||
&& !currentParamName.contains(".") && !currentParamName.contains("(") && !currentParamName.contains("new ");
|
||||
&& (currentParamName.contains("new ") || (!currentParamName.replaceFirst("^this\\.", "").contains(".") && !currentParamName.contains("(")));
|
||||
if (hasGetterOnReceiver) {
|
||||
List<String> getterEvents = evaluateGetterOnReceiver(resolvedValue, methodSuffix, entryMethod, path, callGraph);
|
||||
if (getterEvents != null && !getterEvents.isEmpty()) {
|
||||
@@ -629,6 +662,30 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
});
|
||||
}
|
||||
|
||||
List<String> unpacked = new ArrayList<>();
|
||||
for (String ev : polymorphicEvents) {
|
||||
if (ev != null) {
|
||||
if (ev.startsWith("ENUM_SET:")) {
|
||||
for (String part : ev.substring(9).split(",")) {
|
||||
String clean = constantExtractor.parseEnumSetElement(part);
|
||||
if (clean != null && !unpacked.contains(clean)) {
|
||||
unpacked.add(clean);
|
||||
}
|
||||
}
|
||||
} else if (ev.startsWith("<SYMBOLIC:")) {
|
||||
if (!unpacked.contains(ev)) {
|
||||
unpacked.add(ev);
|
||||
}
|
||||
} else {
|
||||
String clean = constantExtractor.parseEnumSetElement(ev);
|
||||
if (clean != null && !unpacked.contains(clean)) {
|
||||
unpacked.add(clean);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
polymorphicEvents = unpacked;
|
||||
|
||||
polymorphicEvents = polymorphicEvents.stream().distinct().collect(java.util.stream.Collectors.toList());
|
||||
|
||||
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
||||
@@ -644,6 +701,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
return tp;
|
||||
} finally {
|
||||
click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel.clearCurrentPath();
|
||||
}
|
||||
}
|
||||
|
||||
protected MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||
@@ -704,10 +764,23 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
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;
|
||||
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||
TypeDeclaration td = context.getTypeDeclaration(typeName);
|
||||
boolean shouldUnwrap = true;
|
||||
if (td != null) {
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
if (!md.isConstructor()) {
|
||||
shouldUnwrap = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldUnwrap) {
|
||||
Expression firstArg = (Expression) cic.arguments().get(0);
|
||||
String resolved = constantResolver.resolve(firstArg, context);
|
||||
if (resolved != null) {
|
||||
expr = firstArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,10 +883,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
Expression receiver = mi.getExpression();
|
||||
String receiverName = null;
|
||||
String receiverType = null;
|
||||
ClassInstanceCreation directCic = null;
|
||||
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
receiverName = sn.getIdentifier();
|
||||
receiverType = variableTracer.getVariableDeclaredType(entryMethod, receiverName);
|
||||
} else if (receiver instanceof ClassInstanceCreation cic) {
|
||||
directCic = cic;
|
||||
receiverType = cic.getType().toString();
|
||||
} else if (receiver instanceof MethodInvocation receiverMi) {
|
||||
Expression currentMi = receiverMi;
|
||||
while (currentMi instanceof MethodInvocation chainMi) {
|
||||
@@ -858,15 +935,35 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
simpleReceiverType = rawType;
|
||||
}
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType);
|
||||
CompilationUnit contextCu = null;
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String className = entryMethod.substring(0, lastDot);
|
||||
TypeDeclaration entryTd = context.getTypeDeclaration(className);
|
||||
if (entryTd != null) {
|
||||
contextCu = (CompilationUnit) entryTd.getRoot();
|
||||
}
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType, contextCu);
|
||||
if (td == null) {
|
||||
td = context.getTypeDeclaration(simpleReceiverType);
|
||||
}
|
||||
if (td != null) {
|
||||
Map<String, String> fieldValues = new HashMap<>();
|
||||
String varName = receiverName;
|
||||
Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||
if (initExpr instanceof MethodInvocation initMi) {
|
||||
initExpr = unwrapMethodInvocation(initMi, 0, entryMethod);
|
||||
ClassInstanceCreation cic = null;
|
||||
if (directCic != null) {
|
||||
cic = directCic;
|
||||
} else {
|
||||
Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||
if (initExpr instanceof MethodInvocation initMi) {
|
||||
initExpr = unwrapMethodInvocation(initMi, 0, entryMethod);
|
||||
}
|
||||
if (initExpr instanceof ClassInstanceCreation) {
|
||||
cic = (ClassInstanceCreation) initExpr;
|
||||
}
|
||||
}
|
||||
if (!(initExpr instanceof ClassInstanceCreation cic)) {
|
||||
if (cic == null) {
|
||||
return null;
|
||||
}
|
||||
MethodDeclaration getter = null;
|
||||
@@ -883,9 +980,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
if (getter != null && getter.getBody() != null) {
|
||||
fieldValues.putAll(constructorAnalyzer.buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String className = entryMethod.substring(0, lastDot);
|
||||
int lastDotEntry = entryMethod.lastIndexOf('.');
|
||||
if (lastDotEntry > 0) {
|
||||
String className = entryMethod.substring(0, lastDotEntry);
|
||||
String methodName = entryMethod.substring(lastDot + 1);
|
||||
TypeDeclaration entryTd = context.getTypeDeclaration(className);
|
||||
if (entryTd != null) {
|
||||
@@ -962,6 +1059,10 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
return new String[] { paramName.substring(0, bracketIndex), paramName.substring(bracketIndex) + currentSuffix };
|
||||
} else if (dotIndex > 0) {
|
||||
if (paramName.startsWith("this.")) {
|
||||
String afterThis = paramName.substring(5);
|
||||
if ((afterThis.startsWith("get") || afterThis.startsWith("is") || afterThis.contains("(")) && !afterThis.contains(".")) {
|
||||
return new String[] { "this", "." + afterThis + currentSuffix };
|
||||
}
|
||||
int nextDotIndex = paramName.indexOf('.', 5);
|
||||
if (nextDotIndex > 0) {
|
||||
return new String[] { paramName.substring(0, nextDotIndex), paramName.substring(nextDotIndex) + currentSuffix };
|
||||
@@ -1113,6 +1214,163 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
return String.join(" && ", constraints);
|
||||
}
|
||||
|
||||
protected String resolveConstraint(MethodInvocation node, String calledMethod, String baseConstraint) {
|
||||
String resolvedConstraint = baseConstraint;
|
||||
if (node.getExpression() instanceof MethodInvocation miReceiver) {
|
||||
if (miReceiver.getName().getIdentifier().equals("get") && !miReceiver.arguments().isEmpty() && miReceiver.getExpression() != null) {
|
||||
Expression keyExpr = (Expression) miReceiver.arguments().get(0);
|
||||
String mapKeyVar = keyExpr.toString();
|
||||
Map<String, String> keyToType = findMapPopulatedKeys(node, miReceiver.getExpression().toString());
|
||||
|
||||
int lastDot = calledMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String impl = calledMethod.substring(0, lastDot);
|
||||
for (Map.Entry<String, String> entry : keyToType.entrySet()) {
|
||||
String registeredType = entry.getValue();
|
||||
if (registeredType.equals(impl) || context.getImplementations(registeredType).contains(impl) || context.getImplementations(impl).contains(registeredType)) {
|
||||
String mapConstraint = mapKeyVar + " == \"" + entry.getKey() + "\"";
|
||||
if (resolvedConstraint != null && !resolvedConstraint.isEmpty()) {
|
||||
resolvedConstraint = resolvedConstraint + " && " + mapConstraint;
|
||||
} else {
|
||||
resolvedConstraint = mapConstraint;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolvedConstraint;
|
||||
}
|
||||
|
||||
protected String resolveMapValueType(MethodInvocation mi) {
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver == null) return null;
|
||||
|
||||
ITypeBinding binding = receiver.resolveTypeBinding();
|
||||
if (binding != null && binding.getTypeArguments().length >= 2) {
|
||||
ITypeBinding valBinding = binding.getTypeArguments()[1];
|
||||
return valBinding.getErasure().getQualifiedName();
|
||||
}
|
||||
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
|
||||
if (enclosingMethod != null) {
|
||||
for (Object paramObj : enclosingMethod.parameters()) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj;
|
||||
if (svd.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
return extractMapValueTypeFromString(svd.getType().toString(), sn);
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDeclaration enclosingType = findEnclosingType(sn);
|
||||
if (enclosingType != null) {
|
||||
for (FieldDeclaration fd : enclosingType.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
if (fragObj instanceof VariableDeclarationFragment vdf && vdf.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
return extractMapValueTypeFromString(fd.getType().toString(), sn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractMapValueTypeFromString(String typeStr, ASTNode contextNode) {
|
||||
if (typeStr.contains("<")) {
|
||||
String generics = typeStr.substring(typeStr.indexOf('<') + 1, typeStr.lastIndexOf('>'));
|
||||
String[] parts = generics.split(",");
|
||||
if (parts.length >= 2) {
|
||||
String valType = parts[1].trim();
|
||||
TypeDeclaration td = context.getTypeDeclaration(valType);
|
||||
if (td == null) {
|
||||
CompilationUnit cu = (CompilationUnit) contextNode.getRoot();
|
||||
td = context.getTypeDeclaration(valType, cu);
|
||||
}
|
||||
if (td != null) {
|
||||
return context.getFqn(td);
|
||||
}
|
||||
return valType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Map<String, String> findMapPopulatedKeys(ASTNode contextNode, String mapVarName) {
|
||||
Map<String, String> keyToType = new HashMap<>();
|
||||
TypeDeclaration td = findEnclosingType(contextNode);
|
||||
if (td != null) {
|
||||
td.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if (node.getName().getIdentifier().equals("put") && node.getExpression() != null) {
|
||||
String exprStr = node.getExpression().toString();
|
||||
if (exprStr.equals(mapVarName) || exprStr.endsWith("." + mapVarName)) {
|
||||
if (node.arguments().size() >= 2) {
|
||||
Expression keyArg = (Expression) node.arguments().get(0);
|
||||
Expression valArg = (Expression) node.arguments().get(1);
|
||||
|
||||
String keyVal = constantResolver.resolve(keyArg, context);
|
||||
if (keyVal == null) {
|
||||
keyVal = keyArg.toString().replaceAll("[\"']", "");
|
||||
}
|
||||
|
||||
String valType = null;
|
||||
ITypeBinding valBinding = valArg.resolveTypeBinding();
|
||||
if (valBinding != null) {
|
||||
valType = valBinding.getErasure().getQualifiedName();
|
||||
}
|
||||
if (valType == null && valArg instanceof SimpleName sn) {
|
||||
MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
|
||||
if (enclosingMethod != null) {
|
||||
for (Object paramObj : enclosingMethod.parameters()) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj;
|
||||
if (svd.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
if (svd.getType().isSimpleType()) {
|
||||
valType = ((SimpleType) svd.getType()).getName().getFullyQualifiedName();
|
||||
} else {
|
||||
valType = svd.getType().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDeclaration enclosingType = findEnclosingType(sn);
|
||||
if (enclosingType != null) {
|
||||
for (FieldDeclaration fd : enclosingType.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
if (fragObj instanceof VariableDeclarationFragment vdf && vdf.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
if (fd.getType().isSimpleType()) {
|
||||
valType = ((SimpleType) fd.getType()).getName().getFullyQualifiedName();
|
||||
} else {
|
||||
valType = fd.getType().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valType != null && keyVal != null) {
|
||||
TypeDeclaration valTd = context.getTypeDeclaration(valType);
|
||||
if (valTd == null) {
|
||||
CompilationUnit cu = (CompilationUnit) node.getRoot();
|
||||
valTd = context.getTypeDeclaration(valType, cu);
|
||||
}
|
||||
if (valTd != null) {
|
||||
valType = context.getFqn(valTd);
|
||||
}
|
||||
keyToType.put(keyVal, valType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
return keyToType;
|
||||
}
|
||||
|
||||
protected abstract Map<String, List<CallEdge>> buildCallGraph();
|
||||
protected abstract String resolveCalledMethod(MethodInvocation node);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.*;
|
||||
|
||||
@@ -47,6 +48,64 @@ public class CallGraphPathFinder {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
if (start.equals(target)) {
|
||||
result.add(new ArrayList<>(List.of(start)));
|
||||
return result;
|
||||
}
|
||||
if (!visited.add(start)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
List<CallEdge> neighbors = graph.get(start);
|
||||
if (neighbors != null) {
|
||||
for (CallEdge edge : neighbors) {
|
||||
String neighbor = edge.getTargetMethod();
|
||||
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
|
||||
List<String> path = new ArrayList<>(List.of(start, target));
|
||||
result.add(path);
|
||||
} else {
|
||||
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited);
|
||||
for (List<String> subPath : subPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
path.addAll(subPath);
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle inheritance fallback for concrete class methods not in the graph
|
||||
if (result.isEmpty() && start.contains(".")) {
|
||||
String className = start.substring(0, start.lastIndexOf('.'));
|
||||
String methodName = start.substring(start.lastIndexOf('.') + 1);
|
||||
if (className.contains("<")) {
|
||||
className = className.substring(0, className.indexOf('<'));
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td != null) {
|
||||
String superclass = context.getSuperclassFqn(td);
|
||||
if (superclass != null) {
|
||||
String superMethod = superclass + "." + methodName;
|
||||
if (graph.containsKey(superMethod)) {
|
||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited);
|
||||
for (List<String> superPath : superPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
path.addAll(superPath);
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visited.remove(start);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||
for (String node : path) {
|
||||
List<CallEdge> edges = callGraph.get(node);
|
||||
|
||||
@@ -341,6 +341,9 @@ public class ConstantExtractor {
|
||||
}
|
||||
if (!handled && variableTracer != null && constructorAnalyzer != null) {
|
||||
List<Expression> tracedRetExprs = variableTracer.traceVariableAll(retExpr);
|
||||
if (tracedRetExprs.isEmpty() && retExpr != null) {
|
||||
tracedRetExprs = List.of(retExpr);
|
||||
}
|
||||
for (Expression tracedRetExpr : tracedRetExprs) {
|
||||
extractConstantsFromExpression(tracedRetExpr, constants);
|
||||
String val = constantResolver.resolve(tracedRetExpr, context);
|
||||
|
||||
@@ -245,7 +245,8 @@ public class ConstructorAnalyzer {
|
||||
boolean matches = false;
|
||||
if (resolvedCtor != null && ctor.resolveBinding() != null) {
|
||||
matches = resolvedCtor.isEqualTo(ctor.resolveBinding()) || resolvedCtor.getKey().equals(ctor.resolveBinding().getKey());
|
||||
} else {
|
||||
}
|
||||
if (!matches) {
|
||||
matches = ctor.parameters().size() == cic.arguments().size();
|
||||
}
|
||||
if (!matches) continue;
|
||||
@@ -271,7 +272,7 @@ public class ConstructorAnalyzer {
|
||||
if (resolved != null) paramValues.put(pName, resolved);
|
||||
}
|
||||
}
|
||||
if (paramValues.isEmpty()) continue;
|
||||
// Evaluate constructor body regardless of whether parameters could be resolved as constants
|
||||
|
||||
ctor.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
@@ -383,6 +384,12 @@ public class ConstructorAnalyzer {
|
||||
resolved = constantResolver.resolve(argExpr, context);
|
||||
}
|
||||
}
|
||||
if (resolved == null && rhsResolver != null) {
|
||||
TypeDeclaration enclosingTd = findEnclosingType(argExpr);
|
||||
if (enclosingTd != null) {
|
||||
resolved = rhsResolver.resolve(argExpr, subClassParamValues, enclosingTd);
|
||||
}
|
||||
}
|
||||
if (resolved != null) {
|
||||
superParamValues.put(pName, resolved);
|
||||
}
|
||||
@@ -472,6 +479,13 @@ public class ConstructorAnalyzer {
|
||||
}
|
||||
|
||||
MethodDeclaration method = context.findMethodDeclaration(td, methodName, true);
|
||||
if (method == null) {
|
||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||
TypeDeclaration targetTd = context.resolveStaticImport(methodName, cu);
|
||||
if (targetTd != null) {
|
||||
method = context.findMethodDeclaration(targetTd, methodName, true);
|
||||
}
|
||||
}
|
||||
if (method == null || method.getBody() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -576,10 +590,12 @@ public class ConstructorAnalyzer {
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, String> fieldValues = buildFieldValuesFromCIC(td, cic, context);
|
||||
Map<String, String> fieldValues = buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor);
|
||||
System.out.println("RESOLVE_GETTER: td=" + context.getFqn(td) + ", fieldValues=" + fieldValues);
|
||||
if (fieldValues.isEmpty()) return Collections.emptyList();
|
||||
|
||||
String result = constantResolver.evaluateMethodBodyWithLocals(getter, fieldValues, context, visited);
|
||||
System.out.println("RESOLVE_GETTER: result=" + result);
|
||||
return result != null ? List.of(result) : Collections.emptyList();
|
||||
} finally {
|
||||
visited.remove(getterKey);
|
||||
@@ -665,6 +681,12 @@ public class ConstructorAnalyzer {
|
||||
if (targetIdx >= 0 && targetIdx < arguments.size()) {
|
||||
Expression arg = (Expression) arguments.get(targetIdx);
|
||||
String val = constantResolver.resolve(arg, context);
|
||||
if (val == null && arg instanceof MethodInvocation mi) {
|
||||
TypeDeclaration enclosingTd = findEnclosingType(arg);
|
||||
if (enclosingTd != null) {
|
||||
val = evaluateMethodCallInConstructor(arg, new HashMap<>(), enclosingTd);
|
||||
}
|
||||
}
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
for (String eVal : val.substring(9).split(",")) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
for (String calledMethod : calledMethods) {
|
||||
String receiver = getReceiverString(node.getExpression());
|
||||
CallEdge edge = new CallEdge(calledMethod, args, receiver);
|
||||
edge.setConstraint(constraint);
|
||||
edge.setConstraint(resolveConstraint(node, calledMethod, constraint));
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
for (Object argObj : node.arguments()) {
|
||||
@@ -166,22 +166,29 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
if (baseCalled == null) return Collections.emptyList();
|
||||
|
||||
List<String> allResolved = new ArrayList<>();
|
||||
allResolved.add(baseCalled);
|
||||
|
||||
if (baseCalled.contains(".")) {
|
||||
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
|
||||
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
|
||||
|
||||
String definingClass = findDefiningClass(className, methodName);
|
||||
if (definingClass != null && !definingClass.equals(className)) {
|
||||
allResolved.set(0, definingClass + "." + methodName);
|
||||
className = definingClass;
|
||||
}
|
||||
|
||||
List<String> impls = context.getImplementations(className);
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
if (impls.isEmpty()) {
|
||||
allResolved.add(className + "." + methodName);
|
||||
} else {
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
boolean isAbstractOrInterface = false;
|
||||
if (td != null) {
|
||||
isAbstractOrInterface = td.isInterface() || (td.modifiers() != null && td.modifiers().toString().contains("abstract"));
|
||||
}
|
||||
if (!isAbstractOrInterface) {
|
||||
allResolved.add(className + "." + methodName);
|
||||
}
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allResolved.add(baseCalled);
|
||||
}
|
||||
|
||||
return allResolved;
|
||||
@@ -619,6 +626,14 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
|
||||
if (receiverType != null) {
|
||||
String rawType = receiverType;
|
||||
if (rawType.contains("<")) {
|
||||
rawType = rawType.substring(0, rawType.indexOf('<'));
|
||||
}
|
||||
if (rawType.equals("java.util.Map") || rawType.equals("Map")) {
|
||||
String valType = resolveMapValueType(mi);
|
||||
if (valType != null) return valType;
|
||||
}
|
||||
if (receiverType.contains("<")) {
|
||||
receiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
for (String calledMethod : calledMethods) {
|
||||
CallEdge edge = new CallEdge(calledMethod, args);
|
||||
edge.setConstraint(constraint);
|
||||
edge.setConstraint(resolveConstraint(node, calledMethod, constraint));
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
for (Object argObj : node.arguments()) {
|
||||
@@ -163,16 +163,29 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
if (baseCalled == null) return Collections.emptyList();
|
||||
|
||||
List<String> allResolved = new ArrayList<>();
|
||||
allResolved.add(baseCalled);
|
||||
|
||||
if (baseCalled.contains(".")) {
|
||||
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
|
||||
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
|
||||
|
||||
List<String> impls = context.getImplementations(className);
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
if (impls.isEmpty()) {
|
||||
allResolved.add(baseCalled);
|
||||
} else {
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
boolean isAbstractOrInterface = false;
|
||||
if (td != null) {
|
||||
isAbstractOrInterface = td.isInterface() || (td.modifiers() != null && td.modifiers().toString().contains("abstract"));
|
||||
}
|
||||
if (!isAbstractOrInterface) {
|
||||
allResolved.add(baseCalled);
|
||||
}
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allResolved.add(baseCalled);
|
||||
}
|
||||
|
||||
return allResolved;
|
||||
@@ -391,6 +404,14 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
|
||||
if (receiverType != null) {
|
||||
String rawType = receiverType;
|
||||
if (rawType.contains("<")) {
|
||||
rawType = rawType.substring(0, rawType.indexOf('<'));
|
||||
}
|
||||
if (rawType.equals("java.util.Map") || rawType.equals("Map")) {
|
||||
String valType = resolveMapValueType(mi);
|
||||
if (valType != null) return valType;
|
||||
}
|
||||
if (receiverType.contains("<")) {
|
||||
receiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||
}
|
||||
|
||||
@@ -285,17 +285,25 @@ public class CodebaseContext {
|
||||
}
|
||||
|
||||
public List<String> getImplementations(String interfaceName) {
|
||||
String cleanName = interfaceName;
|
||||
if (interfaceName != null && interfaceName.contains("<")) {
|
||||
cleanName = interfaceName.substring(0, interfaceName.indexOf('<'));
|
||||
}
|
||||
Set<String> allImpls = new HashSet<>();
|
||||
collectImplementations(interfaceName, allImpls, new HashSet<>());
|
||||
collectImplementations(cleanName, allImpls, new HashSet<>());
|
||||
System.out.println("GET IMPLEMENTATIONS FOR: " + interfaceName + " -> " + allImpls);
|
||||
return new ArrayList<>(allImpls);
|
||||
}
|
||||
|
||||
private void collectImplementations(String typeName, Set<String> results, Set<String> visited) {
|
||||
if (!visited.add(typeName)) return;
|
||||
String cleanName = typeName;
|
||||
if (typeName != null && typeName.contains("<")) {
|
||||
cleanName = typeName.substring(0, typeName.indexOf('<'));
|
||||
}
|
||||
if (!visited.add(cleanName)) return;
|
||||
|
||||
// Try direct match
|
||||
List<String> directImpls = interfaceToImpls.get(typeName);
|
||||
List<String> directImpls = interfaceToImpls.get(cleanName);
|
||||
|
||||
// Try FQN match if input was simple name
|
||||
if (directImpls == null) {
|
||||
|
||||
@@ -9,6 +9,16 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
private final Map<MethodDeclaration, ReachingDefinitions> rdCache = new HashMap<>();
|
||||
private final Map<ASTNode, Map<IVariableBinding, Expression>> objectFieldBindings = new HashMap<>();
|
||||
|
||||
private static final ThreadLocal<List<String>> CURRENT_PATH = new ThreadLocal<>();
|
||||
|
||||
public static void setCurrentPath(List<String> path) {
|
||||
CURRENT_PATH.set(path);
|
||||
}
|
||||
|
||||
public static void clearCurrentPath() {
|
||||
CURRENT_PATH.remove();
|
||||
}
|
||||
|
||||
public JdtDataFlowModel(CodebaseContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
@@ -82,14 +92,16 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Handle Parameter mapping for SimpleName
|
||||
// 3. Handle Parameter Variable Bindings (Propagate values through parameters)
|
||||
if (expr instanceof SimpleName sn) {
|
||||
IBinding b = sn.resolveBinding();
|
||||
if (b instanceof IVariableBinding paramVb && paramBindings.containsKey(paramVb)) {
|
||||
Expression argExpr = paramBindings.get(paramVb);
|
||||
List<Expression> resolved = getReachingDefinitions(argExpr, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
if (b instanceof IVariableBinding paramVb) {
|
||||
if (paramBindings.containsKey(paramVb)) {
|
||||
Expression argExpr = paramBindings.get(paramVb);
|
||||
List<Expression> resolved = getReachingDefinitions(argExpr, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Local Reaching Definitions
|
||||
@@ -134,6 +146,44 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// Handle SwitchExpression
|
||||
if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
|
||||
List<Expression> results = new ArrayList<>();
|
||||
List<Expression> selectorDefs = getReachingDefinitions(se.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
String selectorVal = null;
|
||||
if (!selectorDefs.isEmpty()) {
|
||||
selectorVal = resolveValue(selectorDefs.get(0), context);
|
||||
}
|
||||
|
||||
boolean active = (selectorVal == null);
|
||||
for (Object stmtObj : se.statements()) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
|
||||
if (selectorVal != null) {
|
||||
active = false;
|
||||
for (Object expObj : sc.expressions()) {
|
||||
Expression caseExpr = (Expression) expObj;
|
||||
String caseVal = resolveValue(caseExpr, context);
|
||||
if (caseVal != null && (caseVal.equals(selectorVal) || selectorVal.endsWith("." + caseVal) || caseVal.endsWith("." + selectorVal))) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sc.isDefault()) {
|
||||
active = true;
|
||||
}
|
||||
}
|
||||
} else if (active) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
|
||||
results.addAll(getReachingDefinitions(ys.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||
} else if (stmtObj instanceof ExpressionStatement es) {
|
||||
results.addAll(getReachingDefinitions(es.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.remove(expr);
|
||||
return results;
|
||||
}
|
||||
|
||||
// 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
String mName = mi.getName().getIdentifier();
|
||||
@@ -374,7 +424,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
MethodDeclaration cd = findMethodDeclaration(cb);
|
||||
if (cd == null || cd.getBody() == null) return;
|
||||
|
||||
Map<IVariableBinding, Expression> currentParamValues = new HashMap<>();
|
||||
Map<IVariableBinding, Expression> currentParamValues = new HashMap<>(callerParamValues);
|
||||
List<?> parameters = cd.parameters();
|
||||
int count = Math.min(parameters.size(), arguments.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -386,6 +436,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
currentParamValues.put(paramVb, resolvedArg);
|
||||
}
|
||||
}
|
||||
System.out.println("EVAL_CTOR: cd=" + cd.getName() + ", currentParamValues=" + currentParamValues);
|
||||
|
||||
List<?> statements = cd.getBody().statements();
|
||||
if (!statements.isEmpty()) {
|
||||
@@ -425,7 +476,12 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
|
||||
if (fieldVb != null) {
|
||||
Expression resolvedRhs = resolveParamValue(rhs, currentParamValues);
|
||||
fieldBindings.put(fieldVb, resolvedRhs);
|
||||
List<Expression> evaluated = getReachingDefinitions(resolvedRhs, new HashSet<>(), currentParamValues, fieldBindings, 0);
|
||||
if (!evaluated.isEmpty()) {
|
||||
fieldBindings.put(fieldVb, evaluated.get(0));
|
||||
} else {
|
||||
fieldBindings.put(fieldVb, resolvedRhs);
|
||||
}
|
||||
}
|
||||
return super.visit(assignment);
|
||||
}
|
||||
@@ -533,6 +589,19 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
if (declaringClassFqn != null && !declaringClassFqn.isEmpty()) {
|
||||
// Find all known implementation subclasses in the codebase
|
||||
List<String> implClassFqns = context.getImplementations(declaringClassFqn);
|
||||
List<String> currentPath = CURRENT_PATH.get();
|
||||
if (currentPath != null && implClassFqns != null && !implClassFqns.isEmpty()) {
|
||||
Set<String> contextTypes = getCompatibleContextTypes(currentPath, declaringClassFqn);
|
||||
List<String> filteredImpls = new ArrayList<>();
|
||||
for (String implFqn : implClassFqns) {
|
||||
if (contextTypes.contains(implFqn)) {
|
||||
filteredImpls.add(implFqn);
|
||||
}
|
||||
}
|
||||
if (!filteredImpls.isEmpty()) {
|
||||
implClassFqns = filteredImpls;
|
||||
}
|
||||
}
|
||||
if (implClassFqns != null && !implClassFqns.isEmpty()) {
|
||||
for (String implFqn : implClassFqns) {
|
||||
TypeDeclaration td = context.getTypeDeclaration(implFqn);
|
||||
@@ -895,4 +964,74 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
return firstVal != null ? firstVal : context.resolveExpression(expr);
|
||||
}
|
||||
|
||||
private Set<String> getCompatibleContextTypes(List<String> currentPath, String declaringClassFqn) {
|
||||
Set<String> contextTypes = new HashSet<>();
|
||||
List<String> implementations = context.getImplementations(declaringClassFqn);
|
||||
|
||||
// 1. Direct class name from the path itself (prioritize concrete implementations in the path)
|
||||
for (String methodFqn : currentPath) {
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String pathClass = methodFqn.substring(0, lastDot);
|
||||
if (implementations.contains(pathClass)) {
|
||||
contextTypes.add(pathClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!contextTypes.isEmpty()) {
|
||||
return contextTypes;
|
||||
}
|
||||
|
||||
// 2. Fallback: Inspect fields and local variables/parameters of the classes in the path
|
||||
for (String methodFqn : currentPath) {
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String pathClass = methodFqn.substring(0, lastDot);
|
||||
if (pathClass.equals(declaringClassFqn) || implementations.contains(pathClass)) {
|
||||
contextTypes.add(pathClass);
|
||||
}
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration(pathClass);
|
||||
if (td != null) {
|
||||
// Check fields
|
||||
for (FieldDeclaration fd : td.getFields()) {
|
||||
String fieldType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(fd.getType());
|
||||
TypeDeclaration fieldTd = context.getTypeDeclaration(fieldType);
|
||||
if (fieldTd == null) {
|
||||
CompilationUnit cu = (CompilationUnit) td.getRoot();
|
||||
fieldTd = context.getTypeDeclaration(fieldType, cu);
|
||||
}
|
||||
if (fieldTd != null) {
|
||||
String fieldFqn = context.getFqn(fieldTd);
|
||||
if (fieldFqn.equals(declaringClassFqn) || implementations.contains(fieldFqn)) {
|
||||
contextTypes.add(fieldFqn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check method parameters
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
for (Object paramObj : md.parameters()) {
|
||||
if (paramObj instanceof SingleVariableDeclaration svd) {
|
||||
String paramType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(svd.getType());
|
||||
TypeDeclaration paramTd = context.getTypeDeclaration(paramType);
|
||||
if (paramTd == null) {
|
||||
CompilationUnit cu = (CompilationUnit) td.getRoot();
|
||||
paramTd = context.getTypeDeclaration(paramType, cu);
|
||||
}
|
||||
if (paramTd != null) {
|
||||
String paramFqn = context.getFqn(paramTd);
|
||||
if (paramFqn.equals(declaringClassFqn) || implementations.contains(paramFqn)) {
|
||||
contextTypes.add(paramFqn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contextTypes;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user