stage 3
This commit is contained in:
@@ -33,13 +33,14 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
|
||||
@Override
|
||||
public List<Expression> getReachingDefinitions(Expression expr) {
|
||||
return getReachingDefinitions(expr, new HashSet<>(), new HashMap<>(), 0);
|
||||
return getReachingDefinitions(expr, new HashSet<>(), new HashMap<>(), new HashMap<>(), 0);
|
||||
}
|
||||
|
||||
private List<Expression> getReachingDefinitions(
|
||||
Expression expr,
|
||||
Set<ASTNode> visited,
|
||||
Map<IVariableBinding, Expression> paramBindings,
|
||||
Map<IVariableBinding, Expression> instanceFieldBindings,
|
||||
int depth) {
|
||||
if (expr == null || depth > 50 || !visited.add(expr)) {
|
||||
return List.of();
|
||||
@@ -49,11 +50,11 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
if (expr instanceof ConditionalExpression ce) {
|
||||
String condVal = resolveValue(ce.getExpression(), context);
|
||||
if ("true".equals(condVal)) {
|
||||
List<Expression> resolved = getReachingDefinitions(ce.getThenExpression(), visited, paramBindings, depth + 1);
|
||||
List<Expression> resolved = getReachingDefinitions(ce.getThenExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
} else if ("false".equals(condVal)) {
|
||||
List<Expression> resolved = getReachingDefinitions(ce.getElseExpression(), visited, paramBindings, depth + 1);
|
||||
List<Expression> resolved = getReachingDefinitions(ce.getElseExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
} else {
|
||||
@@ -65,9 +66,16 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
// 2. Handle Field constant / field initializer propagation
|
||||
IVariableBinding vb = getVariableBinding(expr);
|
||||
if (vb != null && vb.isField()) {
|
||||
if (instanceFieldBindings.containsKey(vb)) {
|
||||
Expression fieldVal = instanceFieldBindings.get(vb);
|
||||
List<Expression> resolved = getReachingDefinitions(fieldVal, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
Expression fieldInit = findFieldInitializer(vb, expr);
|
||||
if (fieldInit != null) {
|
||||
List<Expression> resolved = getReachingDefinitions(fieldInit, visited, paramBindings, depth + 1);
|
||||
List<Expression> resolved = getReachingDefinitions(fieldInit, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
@@ -78,7 +86,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
IBinding b = sn.resolveBinding();
|
||||
if (b instanceof IVariableBinding paramVb && paramBindings.containsKey(paramVb)) {
|
||||
Expression argExpr = paramBindings.get(paramVb);
|
||||
List<Expression> resolved = getReachingDefinitions(argExpr, visited, paramBindings, depth + 1);
|
||||
List<Expression> resolved = getReachingDefinitions(argExpr, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
@@ -93,7 +101,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
for (ASTNode def : defs) {
|
||||
Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
|
||||
if (defExpr != null) {
|
||||
exprs.addAll(getReachingDefinitions(defExpr, visited, paramBindings, depth + 1));
|
||||
exprs.addAll(getReachingDefinitions(defExpr, visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||
} else {
|
||||
exprs.add(expr);
|
||||
}
|
||||
@@ -104,15 +112,69 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle CastExpression
|
||||
if (expr instanceof CastExpression ce) {
|
||||
List<Expression> resolved = getReachingDefinitions(ce.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// Handle ParenthesizedExpression
|
||||
if (expr instanceof ParenthesizedExpression pe) {
|
||||
List<Expression> resolved = getReachingDefinitions(pe.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
IMethodBinding mb = mi.resolveMethodBinding();
|
||||
if (mb != null) {
|
||||
List<TargetMethod> targets = resolveTargets(mi, mb, visited, paramBindings, instanceFieldBindings, depth);
|
||||
if (!targets.isEmpty()) {
|
||||
List<Expression> results = new ArrayList<>();
|
||||
for (TargetMethod target : targets) {
|
||||
MethodDeclaration md = target.declaration;
|
||||
if (md != null && md.getBody() != null) {
|
||||
Map<IVariableBinding, Expression> newParamBindings = new HashMap<>(paramBindings);
|
||||
List<?> parameters = md.parameters();
|
||||
List<?> arguments = mi.arguments();
|
||||
int count = Math.min(parameters.size(), arguments.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
|
||||
IVariableBinding paramVb = svd.resolveBinding();
|
||||
if (paramVb != null) {
|
||||
newParamBindings.put(paramVb, (Expression) arguments.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
List<ReturnStatement> returns = findReturnStatements(md.getBody());
|
||||
if (!returns.isEmpty()) {
|
||||
for (ReturnStatement rs : returns) {
|
||||
if (rs.getExpression() != null) {
|
||||
results.addAll(getReachingDefinitions(rs.getExpression(), visited, newParamBindings, target.fieldBindings, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!results.isEmpty()) {
|
||||
visited.remove(expr);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Super Method Invocations
|
||||
if (expr instanceof SuperMethodInvocation smi) {
|
||||
IMethodBinding mb = smi.resolveMethodBinding();
|
||||
if (mb != null) {
|
||||
MethodDeclaration md = findMethodDeclaration(mb);
|
||||
if (md != null && md.getBody() != null) {
|
||||
Map<IVariableBinding, Expression> newParamBindings = new HashMap<>(paramBindings);
|
||||
List<?> parameters = md.parameters();
|
||||
List<?> arguments = mi.arguments();
|
||||
List<?> arguments = smi.arguments();
|
||||
int count = Math.min(parameters.size(), arguments.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
|
||||
@@ -127,7 +189,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
List<Expression> results = new ArrayList<>();
|
||||
for (ReturnStatement rs : returns) {
|
||||
if (rs.getExpression() != null) {
|
||||
results.addAll(getReachingDefinitions(rs.getExpression(), visited, newParamBindings, depth + 1));
|
||||
results.addAll(getReachingDefinitions(rs.getExpression(), visited, newParamBindings, instanceFieldBindings, depth + 1));
|
||||
}
|
||||
}
|
||||
visited.remove(expr);
|
||||
@@ -174,41 +236,297 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
|
||||
if (td != null) {
|
||||
// 1. Try field declaration initializer
|
||||
for (FieldDeclaration fd : td.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
||||
if (fragment.getName().getIdentifier().equals(vb.getName())) {
|
||||
return fragment.getInitializer();
|
||||
if (fragment.getInitializer() != null) {
|
||||
return fragment.getInitializer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Try constructor assignments
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
if (md.isConstructor() && md.getBody() != null) {
|
||||
final Expression[] assignedExpr = new Expression[1];
|
||||
md.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(Assignment assignment) {
|
||||
Expression lhs = assignment.getLeftHandSide();
|
||||
boolean matches = false;
|
||||
if (lhs instanceof SimpleName snLhs && snLhs.getIdentifier().equals(vb.getName())) {
|
||||
IBinding b = snLhs.resolveBinding();
|
||||
if (b instanceof IVariableBinding resolvedVb && resolvedVb.getKey().equals(vb.getKey())) {
|
||||
matches = true;
|
||||
}
|
||||
} else if (lhs instanceof FieldAccess fa && fa.getName().getIdentifier().equals(vb.getName()) && fa.getExpression() instanceof ThisExpression) {
|
||||
IBinding b = fa.getName().resolveBinding();
|
||||
if (b instanceof IVariableBinding resolvedVb && resolvedVb.getKey().equals(vb.getKey())) {
|
||||
matches = true;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
assignedExpr[0] = assignment.getRightHandSide();
|
||||
}
|
||||
return super.visit(assignment);
|
||||
}
|
||||
});
|
||||
if (assignedExpr[0] != null) {
|
||||
return assignedExpr[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<IVariableBinding, Expression> buildFieldBindingsFromCic(ClassInstanceCreation cic) {
|
||||
Map<IVariableBinding, Expression> fieldBindings = new HashMap<>();
|
||||
IMethodBinding cb = cic.resolveConstructorBinding();
|
||||
if (cb == null) return fieldBindings;
|
||||
|
||||
evaluateConstructor(cb, cic.arguments(), new HashMap<>(), fieldBindings, 0);
|
||||
return fieldBindings;
|
||||
}
|
||||
|
||||
private void evaluateConstructor(
|
||||
IMethodBinding cb,
|
||||
List<?> arguments,
|
||||
Map<IVariableBinding, Expression> callerParamValues,
|
||||
Map<IVariableBinding, Expression> fieldBindings,
|
||||
int depth) {
|
||||
if (cb == null || depth > 10) return;
|
||||
MethodDeclaration cd = findMethodDeclaration(cb);
|
||||
if (cd == null || cd.getBody() == null) return;
|
||||
|
||||
Map<IVariableBinding, Expression> currentParamValues = new HashMap<>();
|
||||
List<?> parameters = cd.parameters();
|
||||
int count = Math.min(parameters.size(), arguments.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
|
||||
IVariableBinding paramVb = svd.resolveBinding();
|
||||
if (paramVb != null) {
|
||||
Expression arg = (Expression) arguments.get(i);
|
||||
Expression resolvedArg = resolveParamValue(arg, callerParamValues);
|
||||
currentParamValues.put(paramVb, resolvedArg);
|
||||
}
|
||||
}
|
||||
|
||||
List<?> statements = cd.getBody().statements();
|
||||
if (!statements.isEmpty()) {
|
||||
Object firstStmt = statements.get(0);
|
||||
if (firstStmt instanceof ConstructorInvocation ci) {
|
||||
IMethodBinding targetCb = ci.resolveConstructorBinding();
|
||||
if (targetCb != null) {
|
||||
evaluateConstructor(targetCb, ci.arguments(), currentParamValues, fieldBindings, depth + 1);
|
||||
}
|
||||
} else if (firstStmt instanceof SuperConstructorInvocation sci) {
|
||||
IMethodBinding targetCb = sci.resolveConstructorBinding();
|
||||
if (targetCb != null) {
|
||||
evaluateConstructor(targetCb, sci.arguments(), currentParamValues, fieldBindings, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cd.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(Assignment assignment) {
|
||||
Expression lhs = assignment.getLeftHandSide();
|
||||
Expression rhs = assignment.getRightHandSide();
|
||||
IVariableBinding fieldVb = null;
|
||||
if (lhs instanceof FieldAccess fa) {
|
||||
if (fa.getExpression() instanceof ThisExpression) {
|
||||
IBinding b = fa.getName().resolveBinding();
|
||||
if (b instanceof IVariableBinding vb && vb.isField()) {
|
||||
fieldVb = vb;
|
||||
}
|
||||
}
|
||||
} else if (lhs instanceof SimpleName sn) {
|
||||
IBinding b = sn.resolveBinding();
|
||||
if (b instanceof IVariableBinding vb && vb.isField()) {
|
||||
fieldVb = vb;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldVb != null) {
|
||||
Expression resolvedRhs = resolveParamValue(rhs, currentParamValues);
|
||||
fieldBindings.put(fieldVb, resolvedRhs);
|
||||
}
|
||||
return super.visit(assignment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Expression resolveParamValue(Expression expr, Map<IVariableBinding, Expression> paramValues) {
|
||||
if (expr instanceof SimpleName sn) {
|
||||
IBinding b = sn.resolveBinding();
|
||||
if (b instanceof IVariableBinding vb && paramValues.containsKey(vb)) {
|
||||
return paramValues.get(vb);
|
||||
}
|
||||
} else if (expr instanceof CastExpression ce) {
|
||||
Expression resolved = resolveParamValue(ce.getExpression(), paramValues);
|
||||
if (resolved != ce.getExpression()) {
|
||||
return resolved;
|
||||
}
|
||||
} else if (expr instanceof ParenthesizedExpression pe) {
|
||||
Expression resolved = resolveParamValue(pe.getExpression(), paramValues);
|
||||
if (resolved != pe.getExpression()) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private static class TargetMethod {
|
||||
final MethodDeclaration declaration;
|
||||
final Map<IVariableBinding, Expression> fieldBindings;
|
||||
|
||||
TargetMethod(MethodDeclaration declaration, Map<IVariableBinding, Expression> fieldBindings) {
|
||||
this.declaration = declaration;
|
||||
this.fieldBindings = fieldBindings;
|
||||
}
|
||||
}
|
||||
|
||||
private List<TargetMethod> resolveTargets(
|
||||
MethodInvocation mi,
|
||||
IMethodBinding mb,
|
||||
Set<ASTNode> visited,
|
||||
Map<IVariableBinding, Expression> paramBindings,
|
||||
Map<IVariableBinding, Expression> instanceFieldBindings,
|
||||
int depth) {
|
||||
|
||||
Expression receiver = mi.getExpression();
|
||||
boolean isThisReceiver = (receiver == null || receiver instanceof ThisExpression);
|
||||
|
||||
List<TargetMethod> targets = new ArrayList<>();
|
||||
|
||||
if (receiver != null) {
|
||||
// Find concrete ClassInstanceCreation definitions in the reaching definitions path of the receiver
|
||||
List<Expression> receiverDefs = getReachingDefinitions(receiver, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
List<ClassInstanceCreation> concreteCics = new ArrayList<>();
|
||||
for (Expression rDef : receiverDefs) {
|
||||
if (rDef instanceof ClassInstanceCreation cic) {
|
||||
concreteCics.add(cic);
|
||||
}
|
||||
}
|
||||
|
||||
if (!concreteCics.isEmpty()) {
|
||||
// Type narrowing is active! Only trace through the concrete instantiated types.
|
||||
for (ClassInstanceCreation cic : concreteCics) {
|
||||
ITypeBinding concreteType = null;
|
||||
if (cic.resolveConstructorBinding() != null) {
|
||||
concreteType = cic.resolveConstructorBinding().getDeclaringClass();
|
||||
}
|
||||
if (concreteType == null) {
|
||||
concreteType = cic.resolveTypeBinding();
|
||||
}
|
||||
if (concreteType != null) {
|
||||
MethodDeclaration md = findMethodDeclarationInType(concreteType, mb);
|
||||
if (md != null) {
|
||||
Map<IVariableBinding, Expression> concreteFieldBindings = buildFieldBindingsFromCic(cic);
|
||||
targets.add(new TargetMethod(md, concreteFieldBindings));
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Polymorphic target discovery (including interface implementations)
|
||||
ITypeBinding declaringClass = mb.getDeclaringClass();
|
||||
if (declaringClass != null) {
|
||||
String declaringClassFqn = declaringClass.getErasure().getQualifiedName();
|
||||
if (declaringClassFqn != null && !declaringClassFqn.isEmpty()) {
|
||||
// Find all known implementation subclasses in the codebase
|
||||
List<String> implClassFqns = context.getImplementations(declaringClassFqn);
|
||||
if (implClassFqns != null && !implClassFqns.isEmpty()) {
|
||||
for (String implFqn : implClassFqns) {
|
||||
TypeDeclaration td = context.getTypeDeclaration(implFqn);
|
||||
if (td != null) {
|
||||
MethodDeclaration md = findMethodDeclarationInType(td.resolveBinding(), mb);
|
||||
if (md != null) {
|
||||
// For polymorphic targets with unknown instance, we don't have concrete field bindings
|
||||
Map<IVariableBinding, Expression> targetFieldBindings = isThisReceiver ? new HashMap<>(instanceFieldBindings) : new HashMap<>();
|
||||
targets.add(new TargetMethod(md, targetFieldBindings));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no polymorphic subclass targets were found, fallback to the declared method itself
|
||||
if (targets.isEmpty()) {
|
||||
MethodDeclaration md = findMethodDeclaration(mb);
|
||||
if (md != null) {
|
||||
Map<IVariableBinding, Expression> targetFieldBindings = isThisReceiver ? new HashMap<>(instanceFieldBindings) : new HashMap<>();
|
||||
targets.add(new TargetMethod(md, targetFieldBindings));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
private MethodDeclaration findMethodDeclarationInType(ITypeBinding typeBinding, IMethodBinding mb) {
|
||||
if (typeBinding == null || mb == null) return null;
|
||||
|
||||
// 1. Try to find it in the class hierarchy (including typeBinding itself)
|
||||
ITypeBinding current = typeBinding;
|
||||
while (current != null) {
|
||||
MethodDeclaration md = findMethodInTypeDeclarationOnly(current, mb);
|
||||
if (md != null) return md;
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
|
||||
// 2. Try to find it in the interface hierarchy of typeBinding
|
||||
Set<String> visited = new HashSet<>();
|
||||
Queue<ITypeBinding> queue = new LinkedList<>();
|
||||
queue.add(typeBinding);
|
||||
while (!queue.isEmpty()) {
|
||||
ITypeBinding tb = queue.poll();
|
||||
if (tb == null) continue;
|
||||
String key = tb.getKey();
|
||||
if (key != null && !visited.add(key)) continue;
|
||||
|
||||
MethodDeclaration md = findMethodInTypeDeclarationOnly(tb, mb);
|
||||
if (md != null) return md;
|
||||
|
||||
for (ITypeBinding interf : tb.getInterfaces()) {
|
||||
queue.add(interf);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodDeclaration findMethodInTypeDeclarationOnly(ITypeBinding typeBinding, IMethodBinding mb) {
|
||||
String classFqn = typeBinding.getErasure().getQualifiedName();
|
||||
if (classFqn != null && !classFqn.isEmpty()) {
|
||||
TypeDeclaration td = context.getTypeDeclaration(classFqn);
|
||||
if (td != null) {
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
IMethodBinding candidateMb = md.resolveBinding();
|
||||
if (candidateMb != null && candidateMb.getKey().equals(mb.getKey())) {
|
||||
return md;
|
||||
}
|
||||
}
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
if (md.getName().getIdentifier().equals(mb.getName()) &&
|
||||
md.parameters().size() == mb.getParameterTypes().length) {
|
||||
return md;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodDeclaration findMethodDeclaration(IMethodBinding mb) {
|
||||
if (mb == null) return null;
|
||||
ITypeBinding declaringClass = mb.getDeclaringClass();
|
||||
if (declaringClass == null) return null;
|
||||
String classFqn = declaringClass.getErasure().getQualifiedName();
|
||||
if (classFqn == null || classFqn.isEmpty()) return null;
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration(classFqn);
|
||||
if (td != null) {
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
IMethodBinding candidateMb = md.resolveBinding();
|
||||
if (candidateMb != null && candidateMb.getKey().equals(mb.getKey())) {
|
||||
return md;
|
||||
}
|
||||
}
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
if (md.getName().getIdentifier().equals(mb.getName()) && md.parameters().size() == mb.getParameterTypes().length) {
|
||||
return md;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return findMethodDeclarationInType(mb.getDeclaringClass(), mb);
|
||||
}
|
||||
|
||||
private List<ReturnStatement> findReturnStatements(ASTNode node) {
|
||||
|
||||
Reference in New Issue
Block a user