Skip to content

Commit 3ec824a

Browse files
authored
Merge pull request #18 from IBM/analysis-over-a-single-file
Enable analysis over a single file provided as a string to codeanalyzer.
2 parents 248dae1 + f548a23 commit 3ec824a

File tree

4 files changed

+146
-92
lines changed

4 files changed

+146
-92
lines changed

src/main/java/com/ibm/northstar/CodeAnalyzer.java

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,27 @@
4343
@Command(name = "codeanalyzer", mixinStandardHelpOptions = true, sortOptions = false, version = "codeanalyzer v1.1", description = "Convert java binary (*.jar, *.ear, *.war) into a comprehensive system dependency graph.")
4444
public class CodeAnalyzer implements Runnable {
4545

46-
@Option(names = {"-i", "--input"}, required = true, description = "Path to the project root directory.")
46+
@Option(names = {"-i", "--input"}, description = "Path to the project root directory.")
4747
private static String input;
4848

49+
@Option(names = {"-s", "--source-analysis"}, description = "Analyze a single string of java source code instead the project.")
50+
private static String sourceAnalysis;
51+
52+
@Option(names = {"-o", "--output"}, description = "Destination directory to save the output graphs. By default, the SDG formatted as a JSON will be printed to the console.")
53+
private static String output;
54+
4955
@Option(names = {"-b", "--build-cmd"}, description = "Custom build command. Defaults to auto build.")
5056
private static String build;
5157

5258
@Option(names = {"--no-build"}, description = "Do not build your application. Use this option if you have already built your application.")
5359
private static boolean noBuild = false;
5460

55-
@Option(names = {"-a", "--analysis-level"}, description = "[Optional] Level of analysis to perform. Options: 1 (for just symbol table) or 2 (for full analysis including the system depenedency graph). Default: 1")
61+
@Option(names = {"-a", "--analysis-level"}, description = "Level of analysis to perform. Options: 1 (for just symbol table) or 2 (for full analysis including the system depenedency graph). Default: 1")
5662
private static int analysisLevel = 1;
5763

58-
@Option(names = {"-o", "--output"}, description = "[Optional] Destination directory to save the output graphs. By default, the SDG formatted as a JSON will be printed to the console.")
59-
private static String output;
60-
61-
@Option(names = {"-d", "--dependencies"}, description = "[Optional] Path to the application 3rd party dependencies that may be helpful in analyzing the application.")
64+
@Option(names = {"-d", "--dependencies"}, description = "Path to the application 3rd party dependencies that may be helpful in analyzing the application.")
6265
private static String dependencies;
6366

64-
@Option(names = {"-s", "--source-analysis"}, description = "[Experimental] Analyze the source code instead directly of the binary. Warning: This option is experimental and may not work as expected.")
65-
private static boolean analyzeSource = false;
66-
6767
@Option(names = {"-v", "--verbose"}, description = "Print logs to console.")
6868
private static boolean verbose = false;
6969

@@ -96,42 +96,55 @@ public void run() {
9696

9797
private static void analyze() throws IOException, ClassHierarchyException, CallGraphBuilderCancelException {
9898

99-
// download library dependencies of project for type resolution
100-
if (!BuildProject.downloadLibraryDependencies(input)) {
101-
Log.warn("Failed to download library dependencies of project");
99+
JsonObject combinedJsonObject = new JsonObject();
100+
Map<String, JavaCompilationUnit> symbolTable;
101+
// First of all if, sourceAnalysis is provided, we will analyze the source code instead of the project.
102+
if (sourceAnalysis != null) {
103+
// Construct symbol table for source code
104+
Log.debug("Single file analysis.");
105+
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult = SymbolTable.extractSingle(sourceAnalysis);
106+
symbolTable = symbolTableExtractionResult.getLeft();
102107
}
103-
// construct symbol table for project, write parse problems to file in output directory if specified
104-
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult =
105-
SymbolTable.extractAll(Paths.get(input));
106-
Map<String, JavaCompilationUnit> symbolTable = symbolTableExtractionResult.getLeft();
107-
if (output != null) {
108-
Path outputPath = Paths.get(output);
109-
if (!Files.exists(outputPath)) {
110-
Files.createDirectories(outputPath);
108+
109+
else {
110+
111+
// download library dependencies of project for type resolution
112+
if (!BuildProject.downloadLibraryDependencies(input)) {
113+
Log.warn("Failed to download library dependencies of project");
114+
}
115+
// construct symbol table for project, write parse problems to file in output directory if specified
116+
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult =
117+
SymbolTable.extractAll(Paths.get(input));
118+
119+
symbolTable = symbolTableExtractionResult.getLeft();
120+
if (output != null) {
121+
Path outputPath = Paths.get(output);
122+
if (!Files.exists(outputPath)) {
123+
Files.createDirectories(outputPath);
124+
}
125+
gson.toJson(symbolTableExtractionResult.getRight(), new FileWriter(new File(outputPath.toString(), "parse_errors.json")));
111126
}
112-
gson.toJson(symbolTableExtractionResult.getRight(), new FileWriter(new File(outputPath.toString(), "parse_errors.json")));
113-
}
114127

115-
JsonObject combinedJsonObject = new JsonObject();
116-
if (analysisLevel > 1) {
117-
// Save SDG, IPCFG, and Call graph as JSON
118-
// If noBuild is not true, and build is also not provided, we will use "auto" as the build command
119-
build = build == null ? "auto" : build;
120-
// Is noBuild is true, we will not build the project
121-
build = noBuild ? null : build;
122-
String sdgAsJSONString = SystemDependencyGraph.construct(input, dependencies, build);
123-
JsonElement sdgAsJSONElement = gson.fromJson(sdgAsJSONString, JsonElement.class);
124-
JsonObject sdgAsJSONObject = sdgAsJSONElement.getAsJsonObject();
125-
126-
// We don't really need these fields, so we'll remove it.
127-
sdgAsJSONObject.remove("nodes");
128-
sdgAsJSONObject.remove("creator");
129-
sdgAsJSONObject.remove("version");
130-
131-
// Remove the 'edges' element and move the list of edges up one level
132-
JsonElement edges = sdgAsJSONObject.get("edges");
133-
combinedJsonObject.add("system_dependency_graph", edges);
128+
if (analysisLevel > 1) {
129+
// Save SDG, and Call graph as JSON
130+
// If noBuild is not true, and build is also not provided, we will use "auto" as the build command
131+
build = build == null ? "auto" : build;
132+
// Is noBuild is true, we will not build the project
133+
build = noBuild ? null : build;
134+
String sdgAsJSONString = SystemDependencyGraph.construct(input, dependencies, build);
135+
JsonElement sdgAsJSONElement = gson.fromJson(sdgAsJSONString, JsonElement.class);
136+
JsonObject sdgAsJSONObject = sdgAsJSONElement.getAsJsonObject();
137+
138+
// We don't really need these fields, so we'll remove it.
139+
sdgAsJSONObject.remove("nodes");
140+
sdgAsJSONObject.remove("creator");
141+
sdgAsJSONObject.remove("version");
142+
143+
// Remove the 'edges' element and move the list of edges up one level
144+
JsonElement edges = sdgAsJSONObject.get("edges");
145+
combinedJsonObject.add("system_dependency_graph", edges);
134146

147+
}
135148
}
136149

137150
// Convert the JavaCompilationUnit to JSON and add to consolidated json object

src/main/java/com/ibm/northstar/SymbolTable.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.ibm.northstar;
22

3+
import com.github.javaparser.JavaParser;
34
import com.github.javaparser.ParseResult;
5+
import com.github.javaparser.ParserConfiguration;
46
import com.github.javaparser.Problem;
57
import com.github.javaparser.ast.CompilationUnit;
68
import com.github.javaparser.ast.NodeList;
@@ -16,6 +18,8 @@
1618
import com.github.javaparser.resolution.UnsolvedSymbolException;
1719
import com.github.javaparser.resolution.types.ResolvedType;
1820
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
21+
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
22+
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
1923
import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy;
2024
import com.github.javaparser.utils.ProjectRoot;
2125
import com.github.javaparser.utils.SourceRoot;
@@ -90,7 +94,7 @@ private boolean isMethodSignatureMatch(String fullSignature, String searchSignat
9094
private static JavaCompilationUnit processCompilationUnit(CompilationUnit parseResult) {
9195
JavaCompilationUnit cUnit = new JavaCompilationUnit();
9296

93-
cUnit.setFilePath(parseResult.getStorage().get().getFileName());
97+
cUnit.setFilePath(parseResult.getStorage().map(s -> s.getPath().toString()).orElse("<in-memory>"));
9498

9599
// Add the comment field to the compilation unit
96100
cUnit.setComment(parseResult.getComment().isPresent() ? parseResult.getComment().get().asString() : "");
@@ -573,6 +577,29 @@ public static Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>>
573577
return Pair.of(symbolTable, parseProblems);
574578
}
575579

580+
public static Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> extractSingle(String code) throws IOException {
581+
Map symbolTable = new LinkedHashMap<String, JavaCompilationUnit>();
582+
Map parseProblems = new HashMap<String, List<Problem>>();
583+
// Setting up symbol solvers
584+
CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
585+
combinedTypeSolver.add(new ReflectionTypeSolver());
586+
587+
ParserConfiguration parserConfiguration = new ParserConfiguration();
588+
parserConfiguration.setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver));
589+
590+
JavaParser javaParser = new JavaParser(parserConfiguration);
591+
ParseResult<CompilationUnit> parseResult = javaParser.parse(code);
592+
if (parseResult.isSuccessful()) {
593+
CompilationUnit compilationUnit = parseResult.getResult().get();
594+
Log.debug("Successfully parsed code. Now processing compilation unit");
595+
symbolTable.put("<pseudo-path>", processCompilationUnit(compilationUnit));
596+
} else {
597+
Log.error(parseResult.getProblems().toString());
598+
parseProblems.put("code", parseResult.getProblems());
599+
}
600+
return Pair.of(symbolTable, parseProblems);
601+
}
602+
576603
public static void main(String[] args) throws IOException {
577604
extractAll(Paths.get(args[0]));
578605
}

src/main/java/com/ibm/northstar/SystemDependencyGraph.java

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -104,58 +104,57 @@ private static org.jgrapht.Graph<Pair<String, Callable>, AbstractGraphEdge> buil
104104
// We'll use forward and backward search on the DFS to identify which CFG nodes
105105
// are dominant
106106
// This is a forward DFS search (or exit time first search)
107-
// int dfsNumber = 0;
108-
// Map<Statement, Integer> dfsFinish = HashMapFactory.make();
109-
// Iterator<Statement> search = DFS.iterateFinishTime(sdg, entryPoints.get());
110-
//
111-
// while (search.hasNext()) {
112-
// dfsFinish.put(search.next(), dfsNumber++);
113-
// }
114-
//
115-
// // This is a reverse DFS search (or entry time first search)
116-
// int reverseDfsNumber = 0;
117-
// Map<Statement, Integer> dfsStart = HashMapFactory.make();
118-
// Iterator<Statement> reverseSearch = DFS.iterateDiscoverTime(sdg, entryPoints.get());
119-
//
120-
// while (reverseSearch.hasNext()) {
121-
// dfsStart.put(reverseSearch.next(), reverseDfsNumber++);
122-
// }
107+
int dfsNumber = 0;
108+
Map<Statement, Integer> dfsFinish = HashMapFactory.make();
109+
Iterator<Statement> search = DFS.iterateFinishTime(sdg, entryPoints.get());
110+
111+
while (search.hasNext()) {
112+
dfsFinish.put(search.next(), dfsNumber++);
113+
}
114+
115+
// This is a reverse DFS search (or entry time first search)
116+
int reverseDfsNumber = 0;
117+
Map<Statement, Integer> dfsStart = HashMapFactory.make();
118+
Iterator<Statement> reverseSearch = DFS.iterateDiscoverTime(sdg, entryPoints.get());
119+
120+
while (reverseSearch.hasNext()) {
121+
dfsStart.put(reverseSearch.next(), reverseDfsNumber++);
122+
}
123123

124124
// Populate graph
125-
// sdg.stream()
126-
// .filter(dfsFinish::containsKey)
127-
// .sorted(Comparator.comparingInt(dfsFinish::get))
128-
// .forEach(p -> sdg.getSuccNodes(p).forEachRemaining(s -> {
129-
// if (dfsFinish.containsKey(s)
130-
// && dfsStart.get(p) != null && dfsStart.get(s) != null
131-
// && !((dfsStart.get(p) >= dfsStart.get(s))
132-
// && (dfsFinish.get(p) <= dfsFinish.get(s)))
133-
// && !p.getNode().getMethod().equals(s.getNode().getMethod())) {
134-
//
135-
// // Add the source nodes to the graph as vertices
136-
// Pair<String, Callable> source = Optional.ofNullable(getCallableFromSymbolTable(p.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(p.getNode().getMethod()));
137-
// graph.addVertex(source);
138-
//
139-
// // Add the target nodes to the graph as vertices
140-
// Pair<String, Callable> target = Optional.ofNullable(getCallableFromSymbolTable(s.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(s.getNode().getMethod()));
141-
// graph.addVertex(target);
142-
//
143-
//
144-
// String edgeType = edgeLabels.apply(p, s);
145-
// SystemDepEdge graphEdge = new SystemDepEdge(p, s, edgeType);
146-
// SystemDepEdge cgEdge = (SystemDepEdge) graph.getEdge(source, target);
147-
// if (source.getRight() != null && target.getRight() != null) {
148-
// if (cgEdge == null || !cgEdge.equals(graphEdge)) {
149-
// graph.addEdge(
150-
// source,
151-
// target,
152-
// graphEdge);
153-
// } else {
154-
// graphEdge.incrementWeight();
155-
// }
156-
// }
157-
// }
158-
// }));
125+
sdg.stream()
126+
.filter(dfsFinish::containsKey)
127+
.sorted(Comparator.comparingInt(dfsFinish::get))
128+
.forEach(p -> sdg.getSuccNodes(p).forEachRemaining(s -> {
129+
if (dfsFinish.containsKey(s)
130+
&& dfsStart.get(p) != null && dfsStart.get(s) != null
131+
&& !((dfsStart.get(p) >= dfsStart.get(s))
132+
&& (dfsFinish.get(p) <= dfsFinish.get(s)))
133+
&& !p.getNode().getMethod().equals(s.getNode().getMethod())) {
134+
135+
// Add the source nodes to the graph as vertices
136+
Pair<String, Callable> source = Optional.ofNullable(getCallableFromSymbolTable(p.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(p.getNode().getMethod()));
137+
graph.addVertex(source);
138+
139+
// Add the target nodes to the graph as vertices
140+
Pair<String, Callable> target = Optional.ofNullable(getCallableFromSymbolTable(s.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(s.getNode().getMethod()));
141+
graph.addVertex(target);
142+
143+
String edgeType = edgeLabels.apply(p, s);
144+
SystemDepEdge graphEdge = new SystemDepEdge(p, s, edgeType);
145+
SystemDepEdge cgEdge = (SystemDepEdge) graph.getEdge(source, target);
146+
if (source.getRight() != null && target.getRight() != null) {
147+
if (cgEdge == null || !cgEdge.equals(graphEdge)) {
148+
graph.addEdge(
149+
source,
150+
target,
151+
graphEdge);
152+
} else {
153+
graphEdge.incrementWeight();
154+
}
155+
}
156+
}
157+
}));
159158

160159
callGraph.getEntrypointNodes()
161160
.forEach(p -> {

src/main/java/com/ibm/northstar/entities/SystemDepEdge.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public class SystemDepEdge extends AbstractGraphEdge {
4141
* The Type.
4242
*/
4343
public final String type;
44+
public final String sourceKind;
45+
public final String destinationKind;
4446

4547
/**
4648
* Instantiates a new System dep edge.
@@ -51,6 +53,8 @@ public class SystemDepEdge extends AbstractGraphEdge {
5153
*/
5254
public SystemDepEdge(Statement sourceStatement, Statement destinationStatement, String type) {
5355
super();
56+
this.sourceKind = sourceStatement.getKind().toString();
57+
this.destinationKind = destinationStatement.getKind().toString();
5458
this.type = type;
5559
this.sourcePos = getStatementPosition(sourceStatement);
5660
this.destinationPos = getStatementPosition(destinationStatement);
@@ -69,6 +73,15 @@ public boolean equals(Object o) {
6973
&& this.type.equals(((SystemDepEdge) o).getType());
7074
}
7175

76+
77+
public String getSourceKind() {
78+
return sourceKind;
79+
}
80+
81+
public String getDestinationKind() {
82+
return destinationKind;
83+
}
84+
7285
/**
7386
* Gets type.
7487
*
@@ -98,7 +111,9 @@ public Integer getDestinationPos() {
98111

99112
public Map<String, Attribute> getAttributes() {
100113
Map<String, Attribute> map = new LinkedHashMap<>();
114+
map.put("source_kind", DefaultAttribute.createAttribute(getSourceKind()));
101115
map.put("type", DefaultAttribute.createAttribute(getType()));
116+
map.put("destination_kind", DefaultAttribute.createAttribute(getDestinationKind()));
102117
map.put("weight", DefaultAttribute.createAttribute(String.valueOf(getWeight())));
103118
return map;
104119
}

0 commit comments

Comments
 (0)