gemini back
This commit is contained in:
@@ -445,6 +445,32 @@ public class ConstantResolver {
|
||||
}
|
||||
|
||||
private String resolveManual(SimpleName sn, CodebaseContext context, Set<String> visited) {
|
||||
MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
|
||||
if (enclosingMethod != null) {
|
||||
for (Object paramObj : enclosingMethod.parameters()) {
|
||||
if (paramObj instanceof SingleVariableDeclaration svd) {
|
||||
if (svd.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enclosingMethod.getBody() != null) {
|
||||
boolean[] isLocal = {false};
|
||||
enclosingMethod.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(VariableDeclarationFragment node) {
|
||||
if (node.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
isLocal[0] = true;
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
if (isLocal[0]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeDeclaration td = findEnclosingType(sn);
|
||||
if (td != null) {
|
||||
String fqn = context.getFqn(td);
|
||||
|
||||
@@ -326,9 +326,11 @@ import java.util.*;
|
||||
|
||||
if (varName != null && declaredType == null) {
|
||||
if (!varName.isEmpty() && !"this".equals(varName) && !"super".equals(varName)) {
|
||||
TypeDeclaration currentTd = context.getTypeDeclaration(tp.getClassName());
|
||||
String entryClassName = entryMethod.substring(0, entryMethod.lastIndexOf('.'));
|
||||
String entryMethodName = entryMethod.substring(entryMethod.lastIndexOf('.') + 1);
|
||||
TypeDeclaration currentTd = context.getTypeDeclaration(entryClassName);
|
||||
if (currentTd != null) {
|
||||
MethodDeclaration currentMd = context.findMethodDeclaration(currentTd, tp.getMethodName(), true);
|
||||
MethodDeclaration currentMd = context.findMethodDeclaration(currentTd, entryMethodName, true);
|
||||
if (currentMd != null) {
|
||||
final String[] extractedFinalTraced = {null, null};
|
||||
final String targetVar = varName;
|
||||
@@ -561,24 +563,93 @@ import java.util.*;
|
||||
|
||||
protected boolean isTypeCompatible(String actualType, String expectedType) {
|
||||
if (actualType == null || expectedType == null) return true;
|
||||
if (expectedType.length() == 1 && Character.isUpperCase(expectedType.charAt(0))) return true;
|
||||
if (actualType.length() == 1 && Character.isUpperCase(actualType.charAt(0))) return true;
|
||||
|
||||
// Save original expectedType to check generic type arguments
|
||||
String originalExpectedType = expectedType;
|
||||
|
||||
// Unwrap collections
|
||||
if (actualType.contains("<")) {
|
||||
String rawType = actualType.substring(0, actualType.indexOf('<'));
|
||||
if (rawType.equals("List") || rawType.equals("Set") || rawType.equals("Collection") || rawType.equals("Iterable") || rawType.endsWith("List") || rawType.endsWith("Set") || rawType.endsWith("Collection")) {
|
||||
int start = actualType.indexOf('<');
|
||||
int end = actualType.lastIndexOf('>');
|
||||
if (start >= 0 && end > start) {
|
||||
actualType = actualType.substring(start + 1, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expectedType.contains("<")) {
|
||||
String rawType = expectedType.substring(0, expectedType.indexOf('<'));
|
||||
if (rawType.equals("List") || rawType.equals("Set") || rawType.equals("Collection") || rawType.equals("Iterable") || rawType.endsWith("List") || rawType.endsWith("Set") || rawType.endsWith("Collection")) {
|
||||
int start = expectedType.indexOf('<');
|
||||
int end = expectedType.lastIndexOf('>');
|
||||
if (start >= 0 && end > start) {
|
||||
expectedType = expectedType.substring(start + 1, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip any remaining generic arguments from raw types
|
||||
if (actualType.contains("<")) {
|
||||
actualType = actualType.substring(0, actualType.indexOf('<'));
|
||||
}
|
||||
if (expectedType.contains("<")) {
|
||||
expectedType = expectedType.substring(0, expectedType.indexOf('<'));
|
||||
}
|
||||
|
||||
if (actualType.equals(expectedType)) return true;
|
||||
if (actualType.endsWith("." + expectedType) || expectedType.endsWith("." + actualType)) return true;
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration(actualType);
|
||||
AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(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;
|
||||
if (td instanceof TypeDeclaration typeDecl) {
|
||||
String superFqn = context.getSuperclassFqn(typeDecl);
|
||||
while (superFqn != null) {
|
||||
if (superFqn.contains("<")) {
|
||||
superFqn = superFqn.substring(0, superFqn.indexOf('<'));
|
||||
}
|
||||
if (superFqn.equals(expectedType) || superFqn.endsWith("." + expectedType) || expectedType.endsWith("." + superFqn)) 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;
|
||||
List interfaces = java.util.Collections.emptyList();
|
||||
if (td instanceof TypeDeclaration typeDecl) {
|
||||
interfaces = typeDecl.superInterfaceTypes();
|
||||
} else if (td instanceof EnumDeclaration enumDecl) {
|
||||
interfaces = enumDecl.superInterfaceTypes();
|
||||
} else if (td instanceof RecordDeclaration recordDecl) {
|
||||
interfaces = recordDecl.superInterfaceTypes();
|
||||
}
|
||||
for (Object interfaceType : interfaces) {
|
||||
String itStr = interfaceType.toString();
|
||||
if (itStr.contains("<")) {
|
||||
itStr = itStr.substring(0, itStr.indexOf('<'));
|
||||
}
|
||||
if (itStr.equals(expectedType) || itStr.endsWith("." + expectedType) || expectedType.endsWith("." + itStr)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Check if the actualType matches any of the generic type arguments of expectedType
|
||||
if (originalExpectedType.contains("<")) {
|
||||
int start = originalExpectedType.indexOf('<');
|
||||
int end = originalExpectedType.lastIndexOf('>');
|
||||
if (start >= 0 && end > start) {
|
||||
String sub = originalExpectedType.substring(start + 1, end);
|
||||
for (String part : sub.split(",")) {
|
||||
String typeArg = part.trim();
|
||||
if (isTypeCompatible(actualType, typeArg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
|
||||
if (methodFqn == null) return null;
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
@@ -1944,13 +2015,58 @@ import java.util.*;
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isFullyQualifiedMethod(String methodFqn) {
|
||||
if (methodFqn == null || !methodFqn.contains(".")) return false;
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
String classPart = methodFqn.substring(0, lastDot);
|
||||
if (classPart.contains(".")) return true;
|
||||
if (!classPart.isEmpty() && Character.isUpperCase(classPart.charAt(0))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isHeuristicMatch(String neighbor, String target) {
|
||||
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);
|
||||
if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) {
|
||||
int lastDotNeighbor = neighbor.lastIndexOf('.');
|
||||
int lastDotTarget = target.lastIndexOf('.');
|
||||
if (lastDotNeighbor < 0 || lastDotTarget < 0) return false;
|
||||
|
||||
String methodNeighbor = neighbor.substring(lastDotNeighbor + 1);
|
||||
String methodTarget = target.substring(lastDotTarget + 1);
|
||||
|
||||
if (!methodNeighbor.equals(methodTarget)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String classNeighbor = neighbor.substring(0, lastDotNeighbor);
|
||||
String classTarget = target.substring(0, lastDotTarget);
|
||||
|
||||
if (classNeighbor.equals(classTarget)) return true;
|
||||
|
||||
String simpleClassNeighbor = classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||
|
||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||
if (simpleClassNeighbor.contains(simpleClassTarget) || simpleClassTarget.contains(simpleClassNeighbor)) return true;
|
||||
|
||||
// Check context implementations
|
||||
List<String> impls = context.getImplementations(classTarget);
|
||||
if (impls != null) {
|
||||
for (String impl : impls) {
|
||||
if (impl.equals(classNeighbor) || impl.endsWith("." + classNeighbor)) return true;
|
||||
}
|
||||
}
|
||||
List<String> implsN = context.getImplementations(classNeighbor);
|
||||
if (implsN != null) {
|
||||
for (String impl : implsN) {
|
||||
if (impl.equals(classTarget) || impl.endsWith("." + classTarget)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fallback for simple names (only if one side is unqualified)
|
||||
|
||||
@@ -675,6 +675,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
.stateMachineId(tp.getStateMachineId())
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(tp.getPolymorphicEvents() != null ? tp.getPolymorphicEvents() : new ArrayList<>())
|
||||
.build();
|
||||
return super.resolveTriggerPointParameters(modifiedTp, path, callGraph);
|
||||
}
|
||||
@@ -722,7 +723,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
String finalResolvedValue = resolvedValue;
|
||||
boolean hasGetterOnReceiver = methodSuffix.startsWith(".get") && currentParamName != null
|
||||
&& !currentParamName.contains(".") && !currentParamName.contains("(") && !currentParamName.contains("new ");
|
||||
if (hasGetterOnReceiver && didThisSuperSubstitution) {
|
||||
if (hasGetterOnReceiver) {
|
||||
List<String> getterEvents = evaluateGetterOnReceiver(finalResolvedValue, methodSuffix, entryMethod, path, callGraph);
|
||||
if (getterEvents != null && !getterEvents.isEmpty()) {
|
||||
return TriggerPoint.builder()
|
||||
@@ -747,6 +748,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
.stateMachineId(tp.getStateMachineId())
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(tp.getPolymorphicEvents() != null ? tp.getPolymorphicEvents() : new ArrayList<>())
|
||||
.build();
|
||||
return super.resolveTriggerPointParameters(modifiedTp, path, callGraph);
|
||||
}
|
||||
@@ -779,27 +781,51 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
if (receiverType != null) {
|
||||
String simpleReceiverType = receiverType;
|
||||
if (receiverType.contains("<")) {
|
||||
simpleReceiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||
String rawType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||
if (rawType.equals("List") || rawType.equals("Set") || rawType.equals("Collection") || rawType.equals("Iterable") || context.getTypeDeclaration(rawType) == null) {
|
||||
int start = receiverType.indexOf('<');
|
||||
int end = receiverType.lastIndexOf('>');
|
||||
if (start >= 0 && end > start) {
|
||||
simpleReceiverType = receiverType.substring(start + 1, end);
|
||||
}
|
||||
} else {
|
||||
simpleReceiverType = rawType;
|
||||
}
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType);
|
||||
if (td != null) {
|
||||
MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true);
|
||||
Map<String, String> fieldValues = new HashMap<>();
|
||||
String varName = receiverName;
|
||||
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||
if (initExpr instanceof org.eclipse.jdt.core.dom.MethodInvocation initMi) {
|
||||
initExpr = unwrapMethodInvocation(initMi, 0);
|
||||
}
|
||||
if (!(initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic)) {
|
||||
return null;
|
||||
}
|
||||
MethodDeclaration getter = null;
|
||||
if (cic.getAnonymousClassDeclaration() != null) {
|
||||
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
||||
if (declObj instanceof MethodDeclaration md && md.getName().getIdentifier().equals(getterName)) {
|
||||
getter = md;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getter == null) {
|
||||
getter = context.findMethodDeclaration(td, getterName, true);
|
||||
}
|
||||
if (getter != null && getter.getBody() != null) {
|
||||
Map<String, String> fieldValues = new HashMap<>();
|
||||
String varName = receiverName;
|
||||
org.eclipse.jdt.core.dom.Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||
if (initExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String className = entryMethod.substring(0, lastDot);
|
||||
String methodName = entryMethod.substring(lastDot + 1);
|
||||
TypeDeclaration entryTd = context.getTypeDeclaration(className);
|
||||
if (entryTd != null) {
|
||||
MethodDeclaration entryMd = context.findMethodDeclaration(entryTd, methodName, false);
|
||||
if (entryMd != null && entryMd.getBody() != null) {
|
||||
// Skip trackSetterCallsBetween since getter call is not in entry method
|
||||
}
|
||||
fieldValues.putAll(buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String className = entryMethod.substring(0, lastDot);
|
||||
String methodName = entryMethod.substring(lastDot + 1);
|
||||
TypeDeclaration entryTd = context.getTypeDeclaration(className);
|
||||
if (entryTd != null) {
|
||||
MethodDeclaration entryMd = context.findMethodDeclaration(entryTd, methodName, false);
|
||||
if (entryMd != null && entryMd.getBody() != null) {
|
||||
// Skip trackSetterCallsBetween since getter call is not in entry method
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -807,9 +833,11 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
if (result != null) {
|
||||
String returnType = getter.getReturnType2() != null ? getter.getReturnType2().toString() : null;
|
||||
if (returnType != null && !result.contains(".")) {
|
||||
int lastDot = returnType.lastIndexOf('.');
|
||||
String simpleReturnType = lastDot > 0 ? returnType.substring(lastDot + 1) : returnType;
|
||||
result = simpleReturnType + "." + result;
|
||||
int lastDotRet = returnType.lastIndexOf('.');
|
||||
String simpleReturnType = lastDotRet > 0 ? returnType.substring(lastDotRet + 1) : returnType;
|
||||
if (!simpleReturnType.equals("String") && !simpleReturnType.equals("Integer") && !simpleReturnType.equals("Long") && !simpleReturnType.equals("Boolean")) {
|
||||
result = simpleReturnType + "." + result;
|
||||
}
|
||||
}
|
||||
return Collections.singletonList(result);
|
||||
}
|
||||
|
||||
@@ -572,15 +572,103 @@ public class CodebaseContext {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFqn(TypeDeclaration td) {
|
||||
StringBuilder sb = new StringBuilder(td.getName().getIdentifier());
|
||||
ASTNode current = td.getParent();
|
||||
while (current instanceof TypeDeclaration parent) {
|
||||
public AbstractTypeDeclaration getAbstractTypeDeclaration(String name) {
|
||||
// Try exact FQN match first
|
||||
CompilationUnit cu = classes.get(name);
|
||||
if (cu != null)
|
||||
return findAbstractTypeInCu(cu, name);
|
||||
|
||||
// If it's a simple name, check if it's ambiguous
|
||||
if (ambiguousSimpleNames.contains(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If not ambiguous, return the one we found during scan
|
||||
String fqn = simpleNameToFqn.get(name);
|
||||
if (fqn != null) {
|
||||
cu = classes.get(fqn);
|
||||
if (cu != null)
|
||||
return findAbstractTypeInCu(cu, fqn);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AbstractTypeDeclaration getAbstractTypeDeclaration(String name, CompilationUnit contextCu) {
|
||||
if (name == null || name.isEmpty())
|
||||
return null;
|
||||
|
||||
// 1. Check if it's already an FQN
|
||||
if (classes.containsKey(name))
|
||||
return getAbstractTypeDeclaration(name);
|
||||
|
||||
// 2. Check imports in contextCu
|
||||
if (contextCu != null) {
|
||||
for (Object impObj : contextCu.imports()) {
|
||||
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||
String impName = imp.getName().getFullyQualifiedName();
|
||||
if (!imp.isStatic() && !imp.isOnDemand()) {
|
||||
if (impName.endsWith("." + name))
|
||||
return getAbstractTypeDeclaration(impName);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check same package
|
||||
String packageName = contextCu.getPackage() != null ? contextCu.getPackage().getName().getFullyQualifiedName() : "";
|
||||
String localFqn = packageName.isEmpty() ? name : packageName + "." + name;
|
||||
if (classes.containsKey(localFqn))
|
||||
return getAbstractTypeDeclaration(localFqn);
|
||||
|
||||
// 4. Check on-demand imports (star imports)
|
||||
for (Object impObj : contextCu.imports()) {
|
||||
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||
if (imp.isOnDemand()) {
|
||||
String starFqn = imp.getName().getFullyQualifiedName() + "." + name;
|
||||
if (classes.containsKey(starFqn))
|
||||
return getAbstractTypeDeclaration(starFqn);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Fallback to java.lang (common)
|
||||
String langFqn = "java.lang." + name;
|
||||
if (classes.containsKey(langFqn))
|
||||
return getAbstractTypeDeclaration(langFqn);
|
||||
}
|
||||
|
||||
// 6. Last resort: global simple name match
|
||||
return getAbstractTypeDeclaration(name);
|
||||
}
|
||||
|
||||
private AbstractTypeDeclaration findAbstractTypeInCu(CompilationUnit cu, String fqn) {
|
||||
for (Object type : cu.types()) {
|
||||
if (type instanceof AbstractTypeDeclaration atd) {
|
||||
AbstractTypeDeclaration found = findNestedAbstractType(atd, fqn);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AbstractTypeDeclaration findNestedAbstractType(AbstractTypeDeclaration atd, String targetFqn) {
|
||||
if (getFqn(atd).equals(targetFqn)) return atd;
|
||||
if (atd instanceof TypeDeclaration td) {
|
||||
for (TypeDeclaration nested : td.getTypes()) {
|
||||
AbstractTypeDeclaration found = findNestedAbstractType(nested, targetFqn);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFqn(AbstractTypeDeclaration atd) {
|
||||
StringBuilder sb = new StringBuilder(atd.getName().getIdentifier());
|
||||
ASTNode current = atd.getParent();
|
||||
while (current instanceof AbstractTypeDeclaration parent) {
|
||||
sb.insert(0, parent.getName().getIdentifier() + ".");
|
||||
current = parent.getParent();
|
||||
}
|
||||
|
||||
CompilationUnit cu = (CompilationUnit) td.getRoot();
|
||||
CompilationUnit cu = (CompilationUnit) atd.getRoot();
|
||||
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
|
||||
String fqn = sb.toString();
|
||||
return packageName.isEmpty() ? fqn : packageName + "." + fqn;
|
||||
|
||||
Reference in New Issue
Block a user