Skip to content

Commit 25ddc98

Browse files
committed
Implement Lightweight Data Flow analysis (Parameters, Variables, Edges)
1 parent 8afcf8b commit 25ddc98

5 files changed

Lines changed: 187 additions & 1 deletion

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.neuvem.java2graph.models;
2+
3+
public class DataFlowEdge {
4+
private String sourceId;
5+
private String targetId;
6+
private String flowType;
7+
8+
public DataFlowEdge(String sourceId, String targetId, String flowType) {
9+
this.sourceId = sourceId;
10+
this.targetId = targetId;
11+
this.flowType = flowType;
12+
}
13+
14+
public String getSourceId() { return sourceId; }
15+
public String getTargetId() { return targetId; }
16+
public String getFlowType() { return flowType; }
17+
18+
@Override
19+
public boolean equals(Object o) {
20+
if (this == o) return true;
21+
if (o == null || getClass() != o.getClass()) return false;
22+
DataFlowEdge that = (DataFlowEdge) o;
23+
return sourceId.equals(that.sourceId) && targetId.equals(that.targetId) && flowType.equals(that.flowType);
24+
}
25+
26+
@Override
27+
public int hashCode() {
28+
int result = sourceId.hashCode();
29+
result = 31 * result + targetId.hashCode();
30+
result = 31 * result + flowType.hashCode();
31+
return result;
32+
}
33+
}

src/main/java/com/neuvem/java2graph/models/GraphContext.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ public class GraphContext {
1111
public final java.util.Set<InheritanceEdge> inheritanceEdges = ConcurrentHashMap.newKeySet();
1212
public final java.util.Set<MethodCallEdge> callEdges = ConcurrentHashMap.newKeySet();
1313
public final java.util.Set<DependencyEdge> dependencyEdges = ConcurrentHashMap.newKeySet();
14+
public final ConcurrentHashMap<String, ParameterNode> parameters = new ConcurrentHashMap<>();
15+
public final ConcurrentHashMap<String, VariableNode> variables = new ConcurrentHashMap<>();
16+
public final java.util.Set<DataFlowEdge> dataFlowEdges = ConcurrentHashMap.newKeySet();
1417

1518
// Store the type solver for use across passes (symbol-solver resolution)
1619
public Object typeSolver;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.neuvem.java2graph.models;
2+
3+
public class ParameterNode {
4+
private String id;
5+
private String methodFqn;
6+
private String name;
7+
private String typeFqn;
8+
private int index;
9+
10+
public ParameterNode() {}
11+
12+
public String getId() { return id; }
13+
public void setId(String id) { this.id = id; }
14+
15+
public String getMethodFqn() { return methodFqn; }
16+
public void setMethodFqn(String methodFqn) { this.methodFqn = methodFqn; }
17+
18+
public String getName() { return name; }
19+
public void setName(String name) { this.name = name; }
20+
21+
public String getTypeFqn() { return typeFqn; }
22+
public void setTypeFqn(String typeFqn) { this.typeFqn = typeFqn; }
23+
24+
public int getIndex() { return index; }
25+
public void setIndex(int index) { this.index = index; }
26+
27+
public static Builder builder() { return new Builder(); }
28+
29+
public static class Builder {
30+
private ParameterNode node = new ParameterNode();
31+
public Builder id(String id) { node.id = id; return this; }
32+
public Builder methodFqn(String methodFqn) { node.methodFqn = methodFqn; return this; }
33+
public Builder name(String name) { node.name = name; return this; }
34+
public Builder typeFqn(String typeFqn) { node.typeFqn = typeFqn; return this; }
35+
public Builder index(int index) { node.index = index; return this; }
36+
public ParameterNode build() { return node; }
37+
}
38+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.neuvem.java2graph.models;
2+
3+
public class VariableNode {
4+
private String id;
5+
private String methodFqn;
6+
private String name;
7+
private String typeFqn;
8+
9+
public VariableNode() {}
10+
11+
public String getId() { return id; }
12+
public void setId(String id) { this.id = id; }
13+
14+
public String getMethodFqn() { return methodFqn; }
15+
public void setMethodFqn(String methodFqn) { this.methodFqn = methodFqn; }
16+
17+
public String getName() { return name; }
18+
public void setName(String name) { this.name = name; }
19+
20+
public String getTypeFqn() { return typeFqn; }
21+
public void setTypeFqn(String typeFqn) { this.typeFqn = typeFqn; }
22+
23+
public static Builder builder() { return new Builder(); }
24+
25+
public static class Builder {
26+
private VariableNode node = new VariableNode();
27+
public Builder id(String id) { node.id = id; return this; }
28+
public Builder methodFqn(String methodFqn) { node.methodFqn = methodFqn; return this; }
29+
public Builder name(String name) { node.name = name; return this; }
30+
public Builder typeFqn(String typeFqn) { node.typeFqn = typeFqn; return this; }
31+
public VariableNode build() { return node; }
32+
}
33+
}

src/main/java/com/neuvem/java2graph/passes/ResolvePass.java

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,17 @@ public void visit(MethodDeclaration n, Void arg) {
610610
.annotations(annotations)
611611
.isLambda(false).filePath(filePath).build());
612612

613+
int pIndex = 0;
614+
for (com.github.javaparser.ast.body.Parameter p : n.getParameters()) {
615+
String paramName = p.getNameAsString();
616+
String paramType = stripGenerics(p.getTypeAsString());
617+
String paramId = fqn + ".param" + pIndex;
618+
context.parameters.put(paramId, ParameterNode.builder()
619+
.id(paramId).methodFqn(fqn)
620+
.name(paramName).typeFqn(paramType).index(pIndex).build());
621+
pIndex++;
622+
}
623+
613624
if (currentClassFqn != null) {
614625
boolean hasAutowired = annotations.stream().anyMatch(a -> a.name.equals("Autowired") || a.name.equals("Inject"));
615626
if (hasAutowired && n.getParameters().isNonEmpty()) {
@@ -649,6 +660,17 @@ public void visit(ConstructorDeclaration n, Void arg) {
649660
.annotations(annotations)
650661
.isLambda(false).filePath(filePath).build());
651662

663+
int pIndex = 0;
664+
for (com.github.javaparser.ast.body.Parameter p : n.getParameters()) {
665+
String paramName = p.getNameAsString();
666+
String paramType = stripGenerics(p.getTypeAsString());
667+
String paramId = fqn + ".param" + pIndex;
668+
context.parameters.put(paramId, ParameterNode.builder()
669+
.id(paramId).methodFqn(fqn)
670+
.name(paramName).typeFqn(paramType).index(pIndex).build());
671+
pIndex++;
672+
}
673+
652674
if (currentClassFqn != null) {
653675
boolean hasAutowired = annotations.stream().anyMatch(a -> a.name.equals("Autowired") || a.name.equals("Inject"));
654676
boolean isImplicit = false;
@@ -699,6 +721,47 @@ public void visit(InitializerDeclaration n, Void arg) {
699721
currentMethodFqn = prev;
700722
}
701723

724+
@Override
725+
public void visit(VariableDeclarator n, Void arg) {
726+
if (currentMethodFqn != null) {
727+
String varName = n.getNameAsString();
728+
String varType = stripGenerics(n.getTypeAsString());
729+
String varId = currentMethodFqn + "." + varName;
730+
context.variables.put(varId, VariableNode.builder()
731+
.id(varId).methodFqn(currentMethodFqn)
732+
.name(varName).typeFqn(varType).build());
733+
}
734+
super.visit(n, arg);
735+
}
736+
737+
@Override
738+
public void visit(AssignExpr n, Void arg) {
739+
if (currentMethodFqn != null) {
740+
String targetId = null;
741+
if (n.getTarget().isFieldAccessExpr()) {
742+
targetId = currentClassFqn + "." + n.getTarget().asFieldAccessExpr().getNameAsString();
743+
} else if (n.getTarget().isNameExpr()) {
744+
String name = n.getTarget().asNameExpr().getNameAsString();
745+
if (searchClassForField(currentClassFqn, name) != null) {
746+
targetId = currentClassFqn + "." + name;
747+
}
748+
}
749+
750+
if (targetId != null) {
751+
String sourceName = null;
752+
if (n.getValue().isNameExpr()) {
753+
sourceName = currentMethodFqn + "." + n.getValue().asNameExpr().getNameAsString();
754+
} else if (n.getValue().isFieldAccessExpr()) {
755+
sourceName = currentClassFqn + "." + n.getValue().asFieldAccessExpr().getNameAsString();
756+
}
757+
if (sourceName != null) {
758+
context.dataFlowEdges.add(new DataFlowEdge(sourceName, targetId, "MUTATES"));
759+
}
760+
}
761+
}
762+
super.visit(n, arg);
763+
}
764+
702765
@Override
703766
public void visit(FieldDeclaration n, Void arg) {
704767
if (currentClassFqn != null) {
@@ -810,8 +873,8 @@ public void visit(LambdaExpr n, Void arg) {
810873

811874
@Override
812875
public void visit(MethodCallExpr n, Void arg) {
876+
String calledFqn = null;
813877
if (currentMethodFqn != null) {
814-
String calledFqn = null;
815878
boolean resolvedViaSolver = false;
816879
if (!config.isFastResolve()) {
817880
try {
@@ -833,6 +896,22 @@ public void visit(MethodCallExpr n, Void arg) {
833896
if (!resolvedViaSolver)
834897
calledFqn = deduceFqnManually(n);
835898
addCall(calledFqn);
899+
900+
int argIndex = 0;
901+
for (Expression argExpr : n.getArguments()) {
902+
if (argExpr.isNameExpr()) {
903+
String argName = argExpr.asNameExpr().getNameAsString();
904+
String sourceId = currentMethodFqn + "." + argName;
905+
String targetId = calledFqn + ".param" + argIndex;
906+
context.dataFlowEdges.add(new DataFlowEdge(sourceId, targetId, "PASSED_TO"));
907+
} else if (argExpr.isFieldAccessExpr()) {
908+
String argName = argExpr.asFieldAccessExpr().getNameAsString();
909+
String sourceId = currentClassFqn + "." + argName;
910+
String targetId = calledFqn + ".param" + argIndex;
911+
context.dataFlowEdges.add(new DataFlowEdge(sourceId, targetId, "PASSED_TO"));
912+
}
913+
argIndex++;
914+
}
836915
}
837916
super.visit(n, arg);
838917
}

0 commit comments

Comments
 (0)