Skip to content

Commit c289cc6

Browse files
committed
Enable analysis over a single file provided as a string to codeanalyzer.
Signed-off-by: Rahul Krishna <i.m.ralk@gmail.com>
1 parent 1794971 commit c289cc6

File tree

4 files changed

+97
-43
lines changed

4 files changed

+97
-43
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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ private static org.jgrapht.Graph<Pair<String, Callable>, AbstractGraphEdge> buil
140140
Pair<String, Callable> target = Optional.ofNullable(getCallableFromSymbolTable(s.getNode().getMethod())).orElseGet(() -> createAndPutNewCallableInSymbolTable(s.getNode().getMethod()));
141141
graph.addVertex(target);
142142

143-
144143
String edgeType = edgeLabels.apply(p, s);
145144
SystemDepEdge graphEdge = new SystemDepEdge(p, s, edgeType);
146145
SystemDepEdge cgEdge = (SystemDepEdge) graph.getEdge(source, target);

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)