Skip to content

Commit

Permalink
Merge pull request #16 from goblint/compilation-db
Browse files Browse the repository at this point in the history
Analyse files according to compilation database
  • Loading branch information
karoliineh authored Jan 20, 2022
2 parents eb16383 + a9fed53 commit 4cb6490
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 143 deletions.
29 changes: 29 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,35 @@ code .
```
The *switch name* (shown in the first column of `opam switch`) is the path to the goblint installation.

### Project prerequisites

The project must have:
1. GobPie configuration file in project root with name "`gobpie.json`"
2. Goblint configuration file ([see examples](https://github.com/goblint/analyzer/tree/master/conf))

#### Gobpie configuration file

Example configuration file `gobpie.json`:
```
{
"goblintConf" : "goblint.json",
"files" : ["./build"],
"preAnalyzeCommand" : ["cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "build"]
}
```

* `goblintConf` - the relative path from project root to the goblint configuration file (required)
* `files` - the relative paths from project root to the files to be analysed (required)
* `preAnalyzeCommand` - the command to run before analysing (e.g. command for building/updating the compilation database for some automation) (optional)

Example values for `files`:
* analyse files according to a compilation database:
* `["."]` (current directory should have the database)
* `["./build"]` (build directory should have the database)
* `["./build/compile_commands.json"]` (direct path to the database, not its directory)
* analyse specified file(s) from the project:
* `["./01-assert.c"]` (single file for analysis without database)
* `["./01-assert.c", "extra.c"]` (multiple files for analysis without database)

## Developing

Expand Down
12 changes: 6 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
<artifactId>goblintanalyzer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.github.magpiebridge</groupId>
<artifactId>magpiebridge</artifactId>
<version>0.1.2</version>
<dependency>
<groupId>com.github.magpiebridge</groupId>
<artifactId>magpiebridge</artifactId>
<version>0.1.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
Expand All @@ -28,12 +28,12 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
<version>2.15.0</version>
</dependency>
</dependencies>
<properties>
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/GobPieConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public class GobPieConfiguration {

private String goblintConf = "";
private String[] files;
private String[] preAnalyzeCommand;

public String getGoblintConf() {
return this.goblintConf;
}

public String[] getFiles() {
return this.files;
}

public String[] getPreAnalyzeCommand() {
return this.preAnalyzeCommand;
}

}
133 changes: 65 additions & 68 deletions src/main/java/GoblintAnalysis.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;

import com.ibm.wala.classLoader.Module;
import com.ibm.wala.classLoader.SourceFileModule;

import magpiebridge.core.AnalysisConsumer;
import magpiebridge.core.AnalysisResult;
import magpiebridge.core.ServerAnalysis;
import magpiebridge.core.MagpieServer;

Expand All @@ -20,8 +18,8 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

Expand All @@ -31,14 +29,19 @@
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.ProcessResult;


public class GoblintAnalysis implements ServerAnalysis {

final private MagpieServer magpieServer;
private URL sourcefileURL;
private String pathToJsonResult = System.getProperty("user.dir") + "/" + "analysisResults.json";
private String[] commands;
private final MagpieServer magpieServer;
private File jsonResult = new File("analysisResults.json");
private File gobPieConf = new File("gobpie.json");
private String pathToGoblintConf;
private String[] filesToAnalyze;
private String[] preAnalyzeCommand;
private String[] goblintRunCommand;
// private List<String> projectFiles; // for future use

private Logger log;
private final Logger log;

public GoblintAnalysis(MagpieServer server) {
this.magpieServer = server;
Expand All @@ -54,53 +57,36 @@ public String source() {
return "GoblintAnalysis";
}

/**
* @return the CLI command including the arguments to be executed.
*/
public String[] getCommand() {
return this.commands;
}

/**
* The files to be analyzed.
*
* @param files the files that have been opened in the editor.
* @param files the files that have been opened in the editor (not using due to using the compilation database).
* @param consumer the server which consumes the analysis results.
* @param rerun tells if the analysis should be reran.
*/
@Override
public void analyze(Collection<? extends Module> files, AnalysisConsumer consumer, boolean rerun) {
if (rerun) {
if (consumer instanceof MagpieServer) {
boolean gobpieconf = readGobPieConfiguration();
if (!gobpieconf) return;
if (preAnalyzeCommand != null && preAnalyzeCommand.length > 0) {
try {
runCommand(new File(System.getProperty("user.dir")), preAnalyzeCommand);
} catch (IOException | InvalidExitValueException | InterruptedException | TimeoutException e) {
this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Warning, "Building compilation database failed. " + e.getMessage()));
}
}
log.info("New analysis started");
MagpieServer server = (MagpieServer) consumer;
Collection<AnalysisResult> results = runAnalysisOnSelectedFiles(files);
server.consume(results, source());
if (generateJson()) server.consume(new ArrayList<>(readResultsFromJson()), source());
}
}
}

/**
* Runs the command on CLI to generate the analysis results for opened files,
* reads in the output and converts it into a collection of AnalysisResults.
*
* @param files the files that have been opened in the editor.
*/
private Collection<AnalysisResult> runAnalysisOnSelectedFiles(Collection<? extends Module> files) {

Collection<AnalysisResult> analysisResults = new ArrayList<>();

for (Module file : files) {
if (file instanceof SourceFileModule) {
boolean successful = generateJson(file);
if (successful) analysisResults.addAll(readResultsFromJson());
}
}
return analysisResults;
}

public ProcessResult runCommand(File dirPath) throws IOException, InvalidExitValueException, InterruptedException, TimeoutException {
String[] command = this.getCommand();
public ProcessResult runCommand(File dirPath, String[] command) throws IOException, InvalidExitValueException, InterruptedException, TimeoutException {
log.debug("Waiting for Goblint to run...");
System.err.println("---------------------- Goblint's dump start ----------------------");
ProcessResult process = new ProcessExecutor()
Expand All @@ -120,23 +106,17 @@ public ProcessResult runCommand(File dirPath) throws IOException, InvalidExitVal
* @param file the file on which to run the analysis.
* @return returns true if goblint finished the analysis and json was generated sucessfully, false otherwise
*/
private boolean generateJson(Module file) {
SourceFileModule sourcefile = (SourceFileModule) file;
try {
// find sourcefile URL
this.sourcefileURL = new URL(magpieServer.getClientUri(sourcefile.getURL().toString()));
// file to be analyzed
String fileToAnalyze = sourcefileURL.getFile();
// construct command to run
this.commands = new String[]{"goblint", "--conf", "goblint.json", "--set", "result", "json-messages", "-o", pathToJsonResult, fileToAnalyze};
} catch (MalformedURLException e) {
log.error("An error occured while trying parse the url of the file to be analyzed. " + e.getMessage());
return false;
}
private boolean generateJson() {
// construct command to run
this.goblintRunCommand = Stream.concat(
Arrays.stream(new String[]{"goblint", "--conf", pathToGoblintConf, "--set", "result", "json-messages", "-o", jsonResult.getAbsolutePath()}),
Arrays.stream(filesToAnalyze))
.toArray(String[]::new);

try {
// run command
log.info("Goblint run with command: " + String.join(" ", this.getCommand()));
ProcessResult commandRunProcess = this.runCommand(new File(System.getProperty("user.dir")));
log.info("Goblint run with command: " + String.join(" ", goblintRunCommand));
ProcessResult commandRunProcess = runCommand(new File(System.getProperty("user.dir")), goblintRunCommand);
if (commandRunProcess.getExitValue() != 0) {
magpieServer.forwardMessageToClient(
new MessageParams(MessageType.Error,
Expand All @@ -147,7 +127,7 @@ private boolean generateJson(Module file) {
log.info("Goblint finished analyzing.");
return true;
} catch (IOException | InvalidExitValueException | InterruptedException | TimeoutException e) {
magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Running Goblint failed. " + e.getMessage()));
this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Running Goblint failed. " + e.getMessage()));
return false;
}
}
Expand All @@ -160,31 +140,48 @@ private boolean generateJson(Module file) {
* @return A collection of GoblintAnalysisResult objects.
*/
private Collection<GoblintAnalysisResult> readResultsFromJson() {

Collection<GoblintAnalysisResult> results = new ArrayList<>();

try {
log.debug("Reading analysis results from json");
// Read json objects as an array
JsonArray resultArray = JsonParser.parseReader(new FileReader(new File(pathToJsonResult))).getAsJsonArray();
JsonObject json = JsonParser.parseReader(new FileReader(jsonResult)).getAsJsonObject();
GsonBuilder builder = new GsonBuilder();
// Add deserializer for tags
builder.registerTypeAdapter(GoblintResult.tag.class, new TagInterfaceAdapter());
builder.registerTypeAdapter(GoblintResult.Message.tag.class, new TagInterfaceAdapter());
Gson gson = builder.create();
// For each JsonObject
for (int i = 0; i < resultArray.size(); i++) {
// Deserailize them into GoblintResult objects
GoblintResult goblintResult = gson.fromJson(resultArray.get(i), GoblintResult.class);
// Convert GoblintResult object to a list of GoblintAnalysisResults
results.addAll(goblintResult.convert());
}
GoblintResult goblintResult = gson.fromJson(json, GoblintResult.class);
Collection<GoblintAnalysisResult> results = goblintResult.convert();
// this.projectFiles = goblintResult.getFiles();
log.debug("Analysis results read from json");

return results;
} catch (JsonIOException | JsonSyntaxException | FileNotFoundException | MalformedURLException e) {
throw new RuntimeException(e);
}
}

return results;
private boolean readGobPieConfiguration() {
try {
log.debug("Reading GobPie configuration from json");
Gson gson = new GsonBuilder().create();
// Read json object
JsonObject jsonObject = JsonParser.parseReader(new FileReader(gobPieConf)).getAsJsonObject();
// Convert json object to GobPieConfiguration object
GobPieConfiguration gobpieConfiguration = gson.fromJson(jsonObject, GobPieConfiguration.class);
this.pathToGoblintConf = new File(gobpieConfiguration.getGoblintConf()).getAbsolutePath().toString();
this.filesToAnalyze = gobpieConfiguration.getFiles();
this.preAnalyzeCommand = gobpieConfiguration.getPreAnalyzeCommand();
if (gobpieConfiguration.getGoblintConf().equals("") || gobpieConfiguration.getFiles() == null || gobpieConfiguration.getFiles().length < 1) {
log.debug("Configuration parameters missing from GobPie configuration file");
magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Configuration parameters missing from GobPie configuration file."));
return false;
}
log.debug("GobPie configuration read from json");
} catch (JsonIOException | JsonSyntaxException e) {
throw new RuntimeException(e);
} catch (FileNotFoundException e) {
this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Could not locate GobPie configuration file. " + e.getMessage()));
return false;
}
return true;
}

}
Loading

0 comments on commit 4cb6490

Please sign in to comment.