stage next
This commit is contained in:
@@ -7,6 +7,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
private final CodebaseContext context;
|
||||
private final Map<MethodDeclaration, ControlFlowGraph> cfgCache = new HashMap<>();
|
||||
private final Map<MethodDeclaration, ReachingDefinitions> rdCache = new HashMap<>();
|
||||
private final Map<ASTNode, Map<IVariableBinding, Expression>> objectFieldBindings = new HashMap<>();
|
||||
|
||||
public JdtDataFlowModel(CodebaseContext context) {
|
||||
this.context = context;
|
||||
@@ -112,6 +113,13 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ClassInstanceCreation
|
||||
if (expr instanceof ClassInstanceCreation cic) {
|
||||
objectFieldBindings.computeIfAbsent(cic, k -> buildFieldBindingsFromCic(cic, paramBindings, instanceFieldBindings));
|
||||
visited.remove(expr);
|
||||
return List.of(cic);
|
||||
}
|
||||
|
||||
// Handle CastExpression
|
||||
if (expr instanceof CastExpression ce) {
|
||||
List<Expression> resolved = getReachingDefinitions(ce.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
@@ -148,11 +156,30 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate mutator/setter method body
|
||||
evaluateMethodMutations(md, mi.arguments(), target.fieldBindings);
|
||||
|
||||
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));
|
||||
Expression retExpr = rs.getExpression();
|
||||
Expression unwrapped = retExpr;
|
||||
while (unwrapped instanceof ParenthesizedExpression pe) {
|
||||
unwrapped = pe.getExpression();
|
||||
}
|
||||
while (unwrapped instanceof CastExpression ce) {
|
||||
unwrapped = ce.getExpression();
|
||||
}
|
||||
if (unwrapped instanceof ThisExpression) {
|
||||
if (mi.getExpression() != null) {
|
||||
results.addAll(getReachingDefinitions(mi.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||
} else {
|
||||
results.add(unwrapped);
|
||||
}
|
||||
} else {
|
||||
results.addAll(getReachingDefinitions(retExpr, visited, newParamBindings, target.fieldBindings, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,11 +311,29 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
|
||||
private Map<IVariableBinding, Expression> buildFieldBindingsFromCic(ClassInstanceCreation cic) {
|
||||
return buildFieldBindingsFromCic(cic, new HashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
private Map<IVariableBinding, Expression> buildFieldBindingsFromCic(
|
||||
ClassInstanceCreation cic,
|
||||
Map<IVariableBinding, Expression> callerParamBindings,
|
||||
Map<IVariableBinding, Expression> callerInstanceFieldBindings) {
|
||||
Map<IVariableBinding, Expression> fieldBindings = new HashMap<>();
|
||||
IMethodBinding cb = cic.resolveConstructorBinding();
|
||||
if (cb == null) return fieldBindings;
|
||||
|
||||
evaluateConstructor(cb, cic.arguments(), new HashMap<>(), fieldBindings, 0);
|
||||
List<Expression> resolvedArguments = new ArrayList<>();
|
||||
for (Object argObj : cic.arguments()) {
|
||||
Expression arg = (Expression) argObj;
|
||||
List<Expression> resolved = getReachingDefinitions(arg, new HashSet<>(), callerParamBindings, callerInstanceFieldBindings, 0);
|
||||
if (!resolved.isEmpty()) {
|
||||
resolvedArguments.add(resolved.get(0));
|
||||
} else {
|
||||
resolvedArguments.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
evaluateConstructor(cb, resolvedArguments, new HashMap<>(), fieldBindings, 0);
|
||||
return fieldBindings;
|
||||
}
|
||||
|
||||
@@ -426,7 +471,26 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
if (concreteType != null) {
|
||||
MethodDeclaration md = findMethodDeclarationInType(concreteType, mb);
|
||||
if (md != null) {
|
||||
Map<IVariableBinding, Expression> concreteFieldBindings = buildFieldBindingsFromCic(cic);
|
||||
Map<IVariableBinding, Expression> concreteFieldBindings =
|
||||
objectFieldBindings.computeIfAbsent(cic, k -> buildFieldBindingsFromCic((ClassInstanceCreation) k));
|
||||
|
||||
// Apply flow-sensitive intermediate local variable mutations
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
|
||||
if (enclosingMethod != null) {
|
||||
ReachingDefinitions rd = getReachingDefinitionsForMethod(enclosingMethod);
|
||||
if (rd != null) {
|
||||
Set<ASTNode> defs = rd.getReachingDefinitions(sn, sn.getIdentifier());
|
||||
for (ASTNode def : defs) {
|
||||
Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
|
||||
if (defExpr == cic || isExpressionWrapping(defExpr, cic)) {
|
||||
applyIntermediateMutations(sn, def, mi, enclosingMethod, concreteFieldBindings, paramBindings, visited, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targets.add(new TargetMethod(md, concreteFieldBindings));
|
||||
}
|
||||
}
|
||||
@@ -456,6 +520,23 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the declaring class is a class (not an interface), or if the declared method itself is concrete/has a body,
|
||||
// we should also include the declared method itself in the polymorphic targets.
|
||||
MethodDeclaration declaredMd = findMethodDeclaration(mb);
|
||||
if (declaredMd != null && declaredMd.getBody() != null) {
|
||||
boolean alreadyAdded = false;
|
||||
for (TargetMethod tm : targets) {
|
||||
if (tm.declaration == declaredMd) {
|
||||
alreadyAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!alreadyAdded) {
|
||||
Map<IVariableBinding, Expression> targetFieldBindings = isThisReceiver ? new HashMap<>(instanceFieldBindings) : new HashMap<>();
|
||||
targets.add(new TargetMethod(declaredMd, targetFieldBindings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no polymorphic subclass targets were found, fallback to the declared method itself
|
||||
@@ -470,6 +551,218 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
return targets;
|
||||
}
|
||||
|
||||
private boolean isExpressionWrapping(Expression outer, Expression inner) {
|
||||
Expression current = outer;
|
||||
while (current != null) {
|
||||
if (current == inner) return true;
|
||||
if (current instanceof ParenthesizedExpression pe) {
|
||||
current = pe.getExpression();
|
||||
} else if (current instanceof CastExpression ce) {
|
||||
current = ce.getExpression();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ControlFlowGraph.CfgNode findCfgNode(ControlFlowGraph cfg, ASTNode astNode) {
|
||||
ASTNode current = astNode;
|
||||
while (current != null) {
|
||||
for (ControlFlowGraph.CfgNode node : cfg.getNodes()) {
|
||||
if (node.getAstNode() == current) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isTargetVariable(Expression expr, IVariableBinding targetVar, String targetName) {
|
||||
if (expr instanceof SimpleName sn) {
|
||||
IBinding b = sn.resolveBinding();
|
||||
if (b instanceof IVariableBinding vb) {
|
||||
if (targetVar != null && vb.getKey().equals(targetVar.getKey())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (targetName != null && sn.getIdentifier().equals(targetName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void evaluateMethodMutations(
|
||||
MethodDeclaration md,
|
||||
List<?> arguments,
|
||||
Map<IVariableBinding, Expression> fieldBindings) {
|
||||
if (md == null || md.getBody() == null) return;
|
||||
|
||||
Map<IVariableBinding, Expression> currentParamValues = new HashMap<>();
|
||||
List<?> parameters = md.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);
|
||||
currentParamValues.put(paramVb, arg);
|
||||
}
|
||||
}
|
||||
|
||||
md.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 void findAndApplyMutations(
|
||||
ASTNode node,
|
||||
IVariableBinding targetVar,
|
||||
String targetName,
|
||||
Map<IVariableBinding, Expression> fieldBindings,
|
||||
Map<IVariableBinding, Expression> paramBindings,
|
||||
Set<ASTNode> visited,
|
||||
int depth) {
|
||||
node.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 (isTargetVariable(fa.getExpression(), targetVar, targetName)) {
|
||||
IBinding b = fa.getName().resolveBinding();
|
||||
if (b instanceof IVariableBinding vb && vb.isField()) {
|
||||
fieldVb = vb;
|
||||
}
|
||||
}
|
||||
} else if (lhs instanceof QualifiedName qn) {
|
||||
if (isTargetVariable(qn.getQualifier(), targetVar, targetName)) {
|
||||
IBinding b = qn.getName().resolveBinding();
|
||||
if (b instanceof IVariableBinding vb && vb.isField()) {
|
||||
fieldVb = vb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldVb != null) {
|
||||
List<Expression> resolvedRhs = getReachingDefinitions(rhs, visited, paramBindings, fieldBindings, depth + 1);
|
||||
if (!resolvedRhs.isEmpty()) {
|
||||
fieldBindings.put(fieldVb, resolvedRhs.get(0));
|
||||
} else {
|
||||
fieldBindings.put(fieldVb, rhs);
|
||||
}
|
||||
}
|
||||
return super.visit(assignment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodInvocation mi) {
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver != null && isTargetVariable(receiver, targetVar, targetName)) {
|
||||
IMethodBinding mb = mi.resolveMethodBinding();
|
||||
if (mb != null) {
|
||||
List<TargetMethod> targets = resolveTargets(mi, mb, visited, paramBindings, fieldBindings, depth + 1);
|
||||
for (TargetMethod target : targets) {
|
||||
if (target.declaration != null) {
|
||||
evaluateMethodMutations(target.declaration, mi.arguments(), fieldBindings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(mi);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void applyIntermediateMutations(
|
||||
SimpleName receiverSimpleName,
|
||||
ASTNode defNode,
|
||||
ASTNode useNode,
|
||||
MethodDeclaration enclosingMethod,
|
||||
Map<IVariableBinding, Expression> fieldBindings,
|
||||
Map<IVariableBinding, Expression> paramBindings,
|
||||
Set<ASTNode> visited,
|
||||
int depth) {
|
||||
|
||||
IBinding b = receiverSimpleName.resolveBinding();
|
||||
IVariableBinding targetVar = (b instanceof IVariableBinding vb) ? vb : null;
|
||||
String targetName = receiverSimpleName.getIdentifier();
|
||||
|
||||
ControlFlowGraph cfg = cfgCache.computeIfAbsent(enclosingMethod, CfgBuilder::build);
|
||||
if (cfg == null) return;
|
||||
|
||||
ControlFlowGraph.CfgNode defCfgNode = findCfgNode(cfg, defNode);
|
||||
ControlFlowGraph.CfgNode useCfgNode = findCfgNode(cfg, useNode);
|
||||
|
||||
if (defCfgNode != null && useCfgNode != null) {
|
||||
Set<ControlFlowGraph.CfgNode> reachFromD = new HashSet<>();
|
||||
Queue<ControlFlowGraph.CfgNode> queue = new LinkedList<>();
|
||||
queue.add(defCfgNode);
|
||||
reachFromD.add(defCfgNode);
|
||||
while (!queue.isEmpty()) {
|
||||
ControlFlowGraph.CfgNode curr = queue.poll();
|
||||
for (ControlFlowGraph.CfgNode succ : curr.getSuccessors()) {
|
||||
if (reachFromD.add(succ)) {
|
||||
queue.add(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<ControlFlowGraph.CfgNode> reachToU = new HashSet<>();
|
||||
queue.clear();
|
||||
queue.add(useCfgNode);
|
||||
reachToU.add(useCfgNode);
|
||||
while (!queue.isEmpty()) {
|
||||
ControlFlowGraph.CfgNode curr = queue.poll();
|
||||
for (ControlFlowGraph.CfgNode pred : curr.getPredecessors()) {
|
||||
if (reachToU.add(pred)) {
|
||||
queue.add(pred);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<ControlFlowGraph.CfgNode> pathNodes = new HashSet<>(reachFromD);
|
||||
pathNodes.retainAll(reachToU);
|
||||
|
||||
List<ControlFlowGraph.CfgNode> orderedPathNodes = new ArrayList<>(pathNodes);
|
||||
List<ControlFlowGraph.CfgNode> allNodes = cfg.getNodes();
|
||||
orderedPathNodes.sort(Comparator.comparingInt(allNodes::indexOf));
|
||||
|
||||
for (ControlFlowGraph.CfgNode node : orderedPathNodes) {
|
||||
if (node != defCfgNode && node != useCfgNode && node.getAstNode() != null) {
|
||||
findAndApplyMutations(node.getAstNode(), targetVar, targetName, fieldBindings, paramBindings, visited, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MethodDeclaration findMethodDeclarationInType(ITypeBinding typeBinding, IMethodBinding mb) {
|
||||
if (typeBinding == null || mb == null) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user