This commit is contained in:
2026-06-27 19:51:26 +02:00
parent 8d66af6d24
commit a7c3b08164
3 changed files with 611 additions and 30 deletions

View File

@@ -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) {

View File

@@ -104,6 +104,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -134,6 +136,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -179,6 +183,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -219,6 +225,8 @@ class JdtDataFlowModelTest {
);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
context.scan(tempDir);
TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
@@ -250,4 +258,259 @@ class JdtDataFlowModelTest {
}
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");
}
}

View File

@@ -338,7 +338,7 @@
},
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payCast", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
"triggerPoint" : {
"event" : "(BaseEvent)new PayEvent()",
"event" : "new PayEvent()",
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
"methodName" : "processEvent",
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",