diff --git a/src/main/java/com/ibm/northstar/CodeAnalyzer.java b/src/main/java/com/ibm/northstar/CodeAnalyzer.java index 3240fd74..b77036d5 100644 --- a/src/main/java/com/ibm/northstar/CodeAnalyzer.java +++ b/src/main/java/com/ibm/northstar/CodeAnalyzer.java @@ -13,8 +13,8 @@ package com.ibm.northstar; - import com.github.javaparser.Problem; +import com.google.common.reflect.TypeToken; import com.google.gson.*; import com.ibm.northstar.entities.JavaCompilationUnit; import com.ibm.northstar.utils.BuildProject; @@ -27,8 +27,10 @@ import picocli.CommandLine.Option; import java.io.File; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -44,6 +46,9 @@ public class CodeAnalyzer implements Runnable { @Option(names = {"-i", "--input"}, description = "Path to the project root directory.") private static String input; + @Option(names = {"-t", "--target-files"}, description = "Paths to files to be analyzed from the input application.") + private static List targetFiles; + @Option(names = {"-s", "--source-analysis"}, description = "Analyze a single string of java source code instead the project.") private static String sourceAnalysis; @@ -62,6 +67,8 @@ public class CodeAnalyzer implements Runnable { @Option(names = {"-v", "--verbose"}, description = "Print logs to console.") private static boolean verbose = false; + private static final String outputFileName = "analysis.json"; + public static Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) @@ -109,11 +116,46 @@ private static void analyze() throws IOException, ClassHierarchyException, CallG } else { Log.warn("Failed to download library dependencies of project"); } - // construct symbol table for project, write parse problems to file in output directory if specified - Pair, Map>> symbolTableExtractionResult = - SymbolTable.extractAll(Paths.get(input)); + boolean analysisFileExists = output != null && Files.exists(Paths.get(output + File.separator + outputFileName)); + + // if target files are specified, compute symbol table information for the given files + if (targetFiles != null) { + Log.info(targetFiles.size() + "target files specified for analysis: " + targetFiles); + + // if target files specified for analysis level 2, downgrade to analysis level 1 + if (analysisLevel > 1) { + Log.warn("Incremental analysis is supported at analysis level 1 only; " + + "performing analysis level 1 for target files"); + analysisLevel = 1; + } + + // extract symbol table for the specified files + symbolTable = SymbolTable.extract(Paths.get(input), targetFiles.stream().map(Paths::get).toList()).getLeft(); + + // if analysis file exists, update it with new symbol table information for the specified fiels + if (analysisFileExists) { + // read symbol table information from existing analysis file + Map existingSymbolTable = readSymbolTableFromFile( + new File(output, outputFileName)); + if (existingSymbolTable != null) { + // for each file, tag its symbol table information as "updated" and update existing symbol table + for (String targetFile : targetFiles) { + String targetPathAbs = Paths.get(targetFile).toAbsolutePath().toString(); + JavaCompilationUnit javaCompilationUnit = symbolTable.get(targetPathAbs); + javaCompilationUnit.setModified(true); + existingSymbolTable.put(targetPathAbs, javaCompilationUnit); + } + } + symbolTable = existingSymbolTable; + } + } + else { + // construct symbol table for project, write parse problems to file in output directory if specified + Pair, Map>> symbolTableExtractionResult = + SymbolTable.extractAll(Paths.get(input)); - symbolTable = symbolTableExtractionResult.getLeft(); + symbolTable = symbolTableExtractionResult.getLeft(); + } if (analysisLevel > 1) { // Save SDG, and Call graph as JSON @@ -166,4 +208,15 @@ private static void emit(String consolidatedJSONString) throws IOException { } } } + + private static Map readSymbolTableFromFile(File analysisJsonFile) { + Type symbolTableType = new TypeToken>() {}.getType(); + try (FileReader reader = new FileReader(analysisJsonFile)) { + JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject(); + return gson.fromJson(jsonObject.get("symbol_table"), symbolTableType); + } catch (IOException e) { + Log.error("Error reading analysis file: " + e.getMessage()); + } + return null; + } } \ No newline at end of file diff --git a/src/main/java/com/ibm/northstar/SymbolTable.java b/src/main/java/com/ibm/northstar/SymbolTable.java index 8ff95358..1182db50 100644 --- a/src/main/java/com/ibm/northstar/SymbolTable.java +++ b/src/main/java/com/ibm/northstar/SymbolTable.java @@ -17,6 +17,7 @@ import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.symbolsolver.JavaSymbolSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import com.github.javaparser.symbolsolver.utils.SymbolSolverCollectionStrategy; import com.github.javaparser.utils.ProjectRoot; @@ -640,8 +641,8 @@ public static Pair, Map>> SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(); ProjectRoot projectRoot = symbolSolverCollectionStrategy.collect(projectRootPath); javaSymbolSolver = (JavaSymbolSolver)symbolSolverCollectionStrategy.getParserConfiguration().getSymbolResolver().get(); - Map symbolTable = new LinkedHashMap(); - Map parseProblems = new HashMap>(); + Map symbolTable = new LinkedHashMap<>(); + Map> parseProblems = new HashMap<>(); for (SourceRoot sourceRoot : projectRoot.getSourceRoots()) { for (ParseResult parseResult : sourceRoot.tryToParse()) { if (parseResult.isSuccessful()) { @@ -681,6 +682,46 @@ public static Pair, Map>> return Pair.of(symbolTable, parseProblems); } + /** + * Parses the given set of Java source files from the given project and constructs the symbol table. + * @param projectRootPath + * @param javaFilePaths + * @return + * @throws IOException + */ + public static Pair, Map>> extract( + Path projectRootPath, + List javaFilePaths + ) throws IOException { + + // create symbol solver and parser configuration + SymbolSolverCollectionStrategy symbolSolverCollectionStrategy = new SymbolSolverCollectionStrategy(); + ProjectRoot projectRoot = symbolSolverCollectionStrategy.collect(projectRootPath); + javaSymbolSolver = (JavaSymbolSolver)symbolSolverCollectionStrategy.getParserConfiguration().getSymbolResolver().get(); + ParserConfiguration parserConfiguration = new ParserConfiguration(); + parserConfiguration.setSymbolResolver(javaSymbolSolver); + + // create java parser with the configuration + JavaParser javaParser = new JavaParser(parserConfiguration); + + Map symbolTable = new LinkedHashMap(); + Map parseProblems = new HashMap>(); + + // parse all given files and return pair of symbol table and parse problems + for (Path javaFilePath : javaFilePaths) { + ParseResult parseResult = javaParser.parse(javaFilePath); + if (parseResult.isSuccessful()) { + CompilationUnit compilationUnit = parseResult.getResult().get(); + symbolTable.put(compilationUnit.getStorage().get().getPath().toString(), + processCompilationUnit(compilationUnit)); + } else { + Log.error(parseResult.getProblems().toString()); + parseProblems.put(javaFilePath.toString(), parseResult.getProblems()); + } + } + return Pair.of(symbolTable, parseProblems); + } + public static void main(String[] args) throws IOException { extractAll(Paths.get(args[0])); } diff --git a/src/main/java/com/ibm/northstar/entities/JavaCompilationUnit.java b/src/main/java/com/ibm/northstar/entities/JavaCompilationUnit.java index 3eb3f9de..86ca0f7f 100644 --- a/src/main/java/com/ibm/northstar/entities/JavaCompilationUnit.java +++ b/src/main/java/com/ibm/northstar/entities/JavaCompilationUnit.java @@ -10,4 +10,5 @@ public class JavaCompilationUnit { private String comment; private List imports; private Map typeDeclarations; + private boolean isModified; }