stage 3
This commit is contained in:
@@ -33,13 +33,14 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Expression> getReachingDefinitions(Expression expr) {
|
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(
|
private List<Expression> getReachingDefinitions(
|
||||||
Expression expr,
|
Expression expr,
|
||||||
Set<ASTNode> visited,
|
Set<ASTNode> visited,
|
||||||
Map<IVariableBinding, Expression> paramBindings,
|
Map<IVariableBinding, Expression> paramBindings,
|
||||||
|
Map<IVariableBinding, Expression> instanceFieldBindings,
|
||||||
int depth) {
|
int depth) {
|
||||||
if (expr == null || depth > 50 || !visited.add(expr)) {
|
if (expr == null || depth > 50 || !visited.add(expr)) {
|
||||||
return List.of();
|
return List.of();
|
||||||
@@ -49,11 +50,11 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
if (expr instanceof ConditionalExpression ce) {
|
if (expr instanceof ConditionalExpression ce) {
|
||||||
String condVal = resolveValue(ce.getExpression(), context);
|
String condVal = resolveValue(ce.getExpression(), context);
|
||||||
if ("true".equals(condVal)) {
|
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);
|
visited.remove(expr);
|
||||||
return resolved;
|
return resolved;
|
||||||
} else if ("false".equals(condVal)) {
|
} 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);
|
visited.remove(expr);
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
@@ -65,9 +66,16 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
// 2. Handle Field constant / field initializer propagation
|
// 2. Handle Field constant / field initializer propagation
|
||||||
IVariableBinding vb = getVariableBinding(expr);
|
IVariableBinding vb = getVariableBinding(expr);
|
||||||
if (vb != null && vb.isField()) {
|
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);
|
Expression fieldInit = findFieldInitializer(vb, expr);
|
||||||
if (fieldInit != null) {
|
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);
|
visited.remove(expr);
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
@@ -78,7 +86,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
IBinding b = sn.resolveBinding();
|
IBinding b = sn.resolveBinding();
|
||||||
if (b instanceof IVariableBinding paramVb && paramBindings.containsKey(paramVb)) {
|
if (b instanceof IVariableBinding paramVb && paramBindings.containsKey(paramVb)) {
|
||||||
Expression argExpr = paramBindings.get(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);
|
visited.remove(expr);
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
@@ -93,7 +101,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
for (ASTNode def : defs) {
|
for (ASTNode def : defs) {
|
||||||
Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
|
Expression defExpr = ReachingDefinitions.getDefinitionExpression(def);
|
||||||
if (defExpr != null) {
|
if (defExpr != null) {
|
||||||
exprs.addAll(getReachingDefinitions(defExpr, visited, paramBindings, depth + 1));
|
exprs.addAll(getReachingDefinitions(defExpr, visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||||
} else {
|
} else {
|
||||||
exprs.add(expr);
|
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)
|
// 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
|
||||||
if (expr instanceof MethodInvocation mi) {
|
if (expr instanceof MethodInvocation mi) {
|
||||||
IMethodBinding mb = mi.resolveMethodBinding();
|
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) {
|
if (mb != null) {
|
||||||
MethodDeclaration md = findMethodDeclaration(mb);
|
MethodDeclaration md = findMethodDeclaration(mb);
|
||||||
if (md != null && md.getBody() != null) {
|
if (md != null && md.getBody() != null) {
|
||||||
Map<IVariableBinding, Expression> newParamBindings = new HashMap<>(paramBindings);
|
Map<IVariableBinding, Expression> newParamBindings = new HashMap<>(paramBindings);
|
||||||
List<?> parameters = md.parameters();
|
List<?> parameters = md.parameters();
|
||||||
List<?> arguments = mi.arguments();
|
List<?> arguments = smi.arguments();
|
||||||
int count = Math.min(parameters.size(), arguments.size());
|
int count = Math.min(parameters.size(), arguments.size());
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) parameters.get(i);
|
||||||
@@ -127,7 +189,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
List<Expression> results = new ArrayList<>();
|
List<Expression> results = new ArrayList<>();
|
||||||
for (ReturnStatement rs : returns) {
|
for (ReturnStatement rs : returns) {
|
||||||
if (rs.getExpression() != null) {
|
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);
|
visited.remove(expr);
|
||||||
@@ -174,41 +236,297 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (td != null) {
|
if (td != null) {
|
||||||
|
// 1. Try field declaration initializer
|
||||||
for (FieldDeclaration fd : td.getFields()) {
|
for (FieldDeclaration fd : td.getFields()) {
|
||||||
for (Object fragObj : fd.fragments()) {
|
for (Object fragObj : fd.fragments()) {
|
||||||
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
||||||
if (fragment.getName().getIdentifier().equals(vb.getName())) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodDeclaration findMethodDeclaration(IMethodBinding mb) {
|
private MethodDeclaration findMethodDeclaration(IMethodBinding mb) {
|
||||||
if (mb == null) return null;
|
if (mb == null) return null;
|
||||||
ITypeBinding declaringClass = mb.getDeclaringClass();
|
return findMethodDeclarationInType(mb.getDeclaringClass(), mb);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ReturnStatement> findReturnStatements(ASTNode node) {
|
private List<ReturnStatement> findReturnStatements(ASTNode node) {
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ class JdtDataFlowModelTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
CodebaseContext context = new CodebaseContext();
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
context.scan(tempDir);
|
context.scan(tempDir);
|
||||||
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
@@ -134,6 +136,8 @@ class JdtDataFlowModelTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
CodebaseContext context = new CodebaseContext();
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
context.scan(tempDir);
|
context.scan(tempDir);
|
||||||
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
@@ -179,6 +183,8 @@ class JdtDataFlowModelTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
CodebaseContext context = new CodebaseContext();
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
context.scan(tempDir);
|
context.scan(tempDir);
|
||||||
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
@@ -219,6 +225,8 @@ class JdtDataFlowModelTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
CodebaseContext context = new CodebaseContext();
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
context.scan(tempDir);
|
context.scan(tempDir);
|
||||||
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
@@ -250,4 +258,259 @@ class JdtDataFlowModelTest {
|
|||||||
}
|
}
|
||||||
assertThat(hasLoopBackToBody).isTrue();
|
assertThat(hasLoopBackToBody).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorAssignmentPropagation(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path comFoo = tempDir.resolve("com/foo");
|
||||||
|
Files.createDirectories(comFoo);
|
||||||
|
Files.writeString(comFoo.resolve("Test.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public class Test {\n" +
|
||||||
|
" private final String myVar;\n" +
|
||||||
|
" public Test() {\n" +
|
||||||
|
" this.myVar = \"CONSTRUCTOR_VAL\";\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public void run() {\n" +
|
||||||
|
" String a = myVar;\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
|
assertThat(td).isNotNull();
|
||||||
|
|
||||||
|
MethodDeclaration md = null;
|
||||||
|
for (MethodDeclaration m : td.getMethods()) {
|
||||||
|
if (m.getName().getIdentifier().equals("run")) {
|
||||||
|
md = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(md).isNotNull();
|
||||||
|
|
||||||
|
VariableDeclarationStatement aStmt = (VariableDeclarationStatement) md.getBody().statements().get(0);
|
||||||
|
VariableDeclarationFragment aFrag = (VariableDeclarationFragment) aStmt.fragments().get(0);
|
||||||
|
Expression initializer = aFrag.getInitializer();
|
||||||
|
|
||||||
|
DataFlowModel model = new JdtDataFlowModel(context);
|
||||||
|
String val = model.resolveValue(initializer, context);
|
||||||
|
assertThat(val).isEqualTo("CONSTRUCTOR_VAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectSensitiveInstanceFieldTracing(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path comFoo = tempDir.resolve("com/foo");
|
||||||
|
Files.createDirectories(comFoo);
|
||||||
|
Files.writeString(comFoo.resolve("Test.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public class Test {\n" +
|
||||||
|
" public static class Builder {\n" +
|
||||||
|
" private final String val;\n" +
|
||||||
|
" public Builder(String val) {\n" +
|
||||||
|
" this.val = val;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public String getVal() {\n" +
|
||||||
|
" return this.val;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public void run() {\n" +
|
||||||
|
" Builder b = new Builder(\"OBJECT_SENSITIVE\");\n" +
|
||||||
|
" String res = b.getVal();\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
|
assertThat(td).isNotNull();
|
||||||
|
|
||||||
|
MethodDeclaration md = null;
|
||||||
|
for (MethodDeclaration m : td.getMethods()) {
|
||||||
|
if (m.getName().getIdentifier().equals("run")) {
|
||||||
|
md = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(md).isNotNull();
|
||||||
|
|
||||||
|
VariableDeclarationStatement resStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment resFrag = (VariableDeclarationFragment) resStmt.fragments().get(0);
|
||||||
|
Expression initializer = resFrag.getInitializer(); // b.getVal()
|
||||||
|
|
||||||
|
DataFlowModel model = new JdtDataFlowModel(context);
|
||||||
|
String val = model.resolveValue(initializer, context);
|
||||||
|
assertThat(val).isEqualTo("OBJECT_SENSITIVE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorChaining(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path comFoo = tempDir.resolve("com/foo");
|
||||||
|
Files.createDirectories(comFoo);
|
||||||
|
Files.writeString(comFoo.resolve("Test.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public class Test {\n" +
|
||||||
|
" public static class Command {\n" +
|
||||||
|
" private final String event;\n" +
|
||||||
|
" private final String info;\n" +
|
||||||
|
" public Command(String event, String info) {\n" +
|
||||||
|
" this.event = event;\n" +
|
||||||
|
" this.info = info;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public Command(String event) {\n" +
|
||||||
|
" this(event, \"DEFAULT_INFO\");\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public Command() {\n" +
|
||||||
|
" this(\"DEFAULT_EVENT\");\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public String getEvent() { return this.event; }\n" +
|
||||||
|
" public String getInfo() { return this.info; }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public void run() {\n" +
|
||||||
|
" Command cmd = new Command();\n" +
|
||||||
|
" String ev = cmd.getEvent();\n" +
|
||||||
|
" String inf = cmd.getInfo();\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
|
assertThat(td).isNotNull();
|
||||||
|
|
||||||
|
MethodDeclaration md = null;
|
||||||
|
for (MethodDeclaration m : td.getMethods()) {
|
||||||
|
if (m.getName().getIdentifier().equals("run")) {
|
||||||
|
md = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(md).isNotNull();
|
||||||
|
|
||||||
|
VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
|
||||||
|
Expression initEv = evFrag.getInitializer();
|
||||||
|
|
||||||
|
DataFlowModel model = new JdtDataFlowModel(context);
|
||||||
|
String valEv = model.resolveValue(initEv, context);
|
||||||
|
assertThat(valEv).isEqualTo("DEFAULT_EVENT");
|
||||||
|
|
||||||
|
VariableDeclarationStatement infStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
|
||||||
|
VariableDeclarationFragment infFrag = (VariableDeclarationFragment) infStmt.fragments().get(0);
|
||||||
|
Expression initInf = infFrag.getInitializer();
|
||||||
|
|
||||||
|
String valInf = model.resolveValue(initInf, context);
|
||||||
|
assertThat(valInf).isEqualTo("DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExplicitSuperCalls(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path comFoo = tempDir.resolve("com/foo");
|
||||||
|
Files.createDirectories(comFoo);
|
||||||
|
Files.writeString(comFoo.resolve("Test.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public class Test {\n" +
|
||||||
|
" public static class Base {\n" +
|
||||||
|
" public String getValue() {\n" +
|
||||||
|
" return \"BASE_VAL\";\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public static class Sub extends Base {\n" +
|
||||||
|
" @Override\n" +
|
||||||
|
" public String getValue() {\n" +
|
||||||
|
" return super.getValue();\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public void run() {\n" +
|
||||||
|
" Sub sub = new Sub();\n" +
|
||||||
|
" String val = sub.getValue();\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
|
assertThat(td).isNotNull();
|
||||||
|
|
||||||
|
MethodDeclaration md = null;
|
||||||
|
for (MethodDeclaration m : td.getMethods()) {
|
||||||
|
if (m.getName().getIdentifier().equals("run")) {
|
||||||
|
md = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(md).isNotNull();
|
||||||
|
|
||||||
|
VariableDeclarationStatement valStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment valFrag = (VariableDeclarationFragment) valStmt.fragments().get(0);
|
||||||
|
Expression initVal = valFrag.getInitializer();
|
||||||
|
|
||||||
|
DataFlowModel model = new JdtDataFlowModel(context);
|
||||||
|
String val = model.resolveValue(initVal, context);
|
||||||
|
assertThat(val).isEqualTo("BASE_VAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDynamicDispatchNarrowing(@TempDir Path tempDir) throws IOException {
|
||||||
|
Path comFoo = tempDir.resolve("com/foo");
|
||||||
|
Files.createDirectories(comFoo);
|
||||||
|
Files.writeString(comFoo.resolve("Test.java"),
|
||||||
|
"package com.foo;\n" +
|
||||||
|
"public class Test {\n" +
|
||||||
|
" public interface Service {\n" +
|
||||||
|
" String getEvent();\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public static class PayService implements Service {\n" +
|
||||||
|
" public String getEvent() { return \"PAY\"; }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public static class CancelService implements Service {\n" +
|
||||||
|
" public String getEvent() { return \"CANCEL\"; }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" public void run() {\n" +
|
||||||
|
" Service s = new PayService();\n" +
|
||||||
|
" String ev = s.getEvent();\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
|
||||||
|
assertThat(td).isNotNull();
|
||||||
|
|
||||||
|
MethodDeclaration md = null;
|
||||||
|
for (MethodDeclaration m : td.getMethods()) {
|
||||||
|
if (m.getName().getIdentifier().equals("run")) {
|
||||||
|
md = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(md).isNotNull();
|
||||||
|
|
||||||
|
VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
|
||||||
|
VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
|
||||||
|
Expression initEv = evFrag.getInitializer();
|
||||||
|
|
||||||
|
DataFlowModel model = new JdtDataFlowModel(context);
|
||||||
|
List<Expression> defs = model.getReachingDefinitions(initEv);
|
||||||
|
assertThat(defs).isNotEmpty();
|
||||||
|
|
||||||
|
String val = model.resolveValue(initEv, context);
|
||||||
|
assertThat(val).isEqualTo("PAY");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -338,7 +338,7 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payCast", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payCast", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "(BaseEvent)new PayEvent()",
|
"event" : "new PayEvent()",
|
||||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||||
"methodName" : "processEvent",
|
"methodName" : "processEvent",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||||
|
|||||||
Reference in New Issue
Block a user