diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1999a37..183d790 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v2 with: - java-version: '11' # TODO: what version do we want? + java-version: '17' # TODO: what version do we want? distribution: 'adopt' cache: maven @@ -42,4 +42,4 @@ jobs: uses: actions/upload-artifact@v2.2.4 with: name: plugin - path: ./vscode/goblintanalyzer-*.vsix + path: ./vscode/gobpie-*.vsix diff --git a/Readme.md b/Readme.md index d0dfab5..4d07a9f 100644 --- a/Readme.md +++ b/Readme.md @@ -30,7 +30,7 @@ Example configuration file `gobpie.json`: { "goblintConf" : "goblint.json", "files" : ["./build"], - "preAnalyzeCommand" : ["cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "build"] + "preAnalyzeCommand" : ["cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-B", "build"] } ``` diff --git a/pom.xml b/pom.xml index 2758bf8..f1cf394 100644 --- a/pom.xml +++ b/pom.xml @@ -1,81 +1,86 @@ - 4.0.0 - magpiebridge - goblintanalyzer - 0.0.1-SNAPSHOT - - - com.github.magpiebridge - magpiebridge - 0.1.3 - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + goblint + gobpie + 0.0.2-SNAPSHOT + - - - com.google.code.gson - gson - 2.8.6 - + + + com.github.magpiebridge + magpiebridge + 0.1.3 + - - org.zeroturnaround - zt-exec - 1.12 - + + + com.google.code.gson + gson + 2.8.6 + - - org.apache.logging.log4j - log4j-api - 2.15.0 - - - org.apache.logging.log4j - log4j-core - 2.15.0 - - - - 1.8 - 1.8 - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - - - package - - shade - - - - - - Main - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - + + + org.zeroturnaround + zt-exec + 1.12 + + + + + org.apache.logging.log4j + log4j-api + 2.15.0 + + + org.apache.logging.log4j + log4j-core + 2.15.0 + + + + + 17 + 17 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + + package + + shade + + + + + + Main + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/GoblintAnalysis.java b/src/main/java/GoblintAnalysis.java deleted file mode 100644 index 6b4d44f..0000000 --- a/src/main/java/GoblintAnalysis.java +++ /dev/null @@ -1,187 +0,0 @@ -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.net.MalformedURLException; -import java.util.*; -import java.util.concurrent.TimeoutException; -import java.util.stream.Stream; - -import com.ibm.wala.classLoader.Module; - -import magpiebridge.core.AnalysisConsumer; -import magpiebridge.core.ServerAnalysis; -import magpiebridge.core.MagpieServer; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; - -import org.eclipse.lsp4j.MessageParams; -import org.eclipse.lsp4j.MessageType; -import org.zeroturnaround.exec.InvalidExitValueException; -import org.zeroturnaround.exec.ProcessExecutor; -import org.zeroturnaround.exec.ProcessResult; - - -public class GoblintAnalysis implements ServerAnalysis { - - 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 projectFiles; // for future use - - private final Logger log; - - public GoblintAnalysis(MagpieServer server) { - this.magpieServer = server; - this.log = LogManager.getLogger(GoblintAnalysis.class); - } - - /** - * The source of this analysis, usually the name of the analysis. - * - * @return the string - */ - public String source() { - return "GoblintAnalysis"; - } - - - /** - * The files to be analyzed. - * - * @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 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; - if (generateJson()) server.consume(new ArrayList<>(readResultsFromJson()), source()); - } - } - } - - - 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() - .directory(dirPath) - .command(command) - .redirectOutput(System.err) - .redirectError(System.err) - .execute(); - System.err.println("----------------------- Goblint's dump end -----------------------"); - return process; - } - - /** - * Runs the command in the project root directory - * to let goblint generate the json file with analysis results. - * - * @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() { - // 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(" ", goblintRunCommand)); - ProcessResult commandRunProcess = runCommand(new File(System.getProperty("user.dir")), goblintRunCommand); - if (commandRunProcess.getExitValue() != 0) { - magpieServer.forwardMessageToClient( - new MessageParams(MessageType.Error, - "Goblint exited with an error.")); - log.error("Goblint exited with an error."); - return false; - } - log.info("Goblint finished analyzing."); - return true; - } catch (IOException | InvalidExitValueException | InterruptedException | TimeoutException e) { - this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Running Goblint failed. " + e.getMessage())); - return false; - } - } - - /** - * Deserializes json to GoblintResult objects and then converts the information - * into GoblintAnalysisResult objects, which Magpie uses to generate IDE - * messages. - * - * @return A collection of GoblintAnalysisResult objects. - */ - private Collection readResultsFromJson() { - try { - log.debug("Reading analysis results from json"); - // Read json objects as an array - JsonObject json = JsonParser.parseReader(new FileReader(jsonResult)).getAsJsonObject(); - GsonBuilder builder = new GsonBuilder(); - // Add deserializer for tags - builder.registerTypeAdapter(GoblintResult.Message.tag.class, new TagInterfaceAdapter()); - Gson gson = builder.create(); - GoblintResult goblintResult = gson.fromJson(json, GoblintResult.class); - Collection 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); - } - } - - 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; - } - -} diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 433de63..737e93d 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,6 +1,3 @@ - -import org.eclipse.lsp4j.jsonrpc.messages.Either; - import java.io.File; import magpiebridge.core.MagpieServer; @@ -8,6 +5,12 @@ import magpiebridge.core.ServerConfiguration; import magpiebridge.core.ToolAnalysis; +import analysis.GoblintAnalysis; +import goblintserver.GoblintClient; +import goblintserver.GoblintServer; + +import org.eclipse.lsp4j.jsonrpc.messages.Either; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,27 +19,49 @@ public class Main { private static final Logger log = LogManager.getLogger(Main.class); public static void main(String... args) { - // create server - MagpieServer server = createServer(); - log.info("Server created"); - // launch the server only if there is a goblint conf file present + + // launch the server only if there is a GobPie conf file present if (new File(System.getProperty("user.dir") + "/" + "gobpie.json").exists()) { - server.launchOnStdio(); - log.info("Server launched"); + + MagpieServer magpieServer = createMagpieServer(); + + if (magpieServer == null) { + log.info("Unable to launch MagpieBridge."); + } else { + magpieServer.launchOnStdio(); + log.info("MagpieBridge server launched."); + magpieServer.doAnalysis("c", true); + } } } - private static MagpieServer createServer() { + + private static MagpieServer createMagpieServer() { + // set up configuration for MagpieServer ServerConfiguration serverConfig = new ServerConfiguration(); - MagpieServer server = new MagpieServer(serverConfig); + serverConfig.setDoAnalysisByFirstOpen(false); + MagpieServer magpieServer = new MagpieServer(serverConfig); + // define language String language = "c"; + + // start GoblintServer + GoblintServer goblintServer = new GoblintServer(magpieServer); + boolean gobServerStarted = goblintServer.startGoblintServer(); + if (!gobServerStarted) return null; + + // connect GoblintClient + GoblintClient goblintClient = new GoblintClient(magpieServer); + boolean goblintClientConnected = goblintClient.connectGoblitClient(); + if (!goblintClientConnected) return null; + // add analysis to the MagpieServer - ServerAnalysis serverAnalysis = new GoblintAnalysis(server); + ServerAnalysis serverAnalysis = new GoblintAnalysis(magpieServer, goblintServer, goblintClient); Either analysis = Either.forLeft(serverAnalysis); - server.addAnalysis(analysis, language); + magpieServer.addAnalysis(analysis, language); - return server; + return magpieServer; } + } diff --git a/src/main/java/analysis/GoblintAnalysis.java b/src/main/java/analysis/GoblintAnalysis.java new file mode 100644 index 0000000..f533d4f --- /dev/null +++ b/src/main/java/analysis/GoblintAnalysis.java @@ -0,0 +1,195 @@ +package analysis; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.*; +import java.util.concurrent.TimeoutException; + +import com.ibm.wala.classLoader.Module; + +import magpiebridge.core.AnalysisConsumer; +import magpiebridge.core.ServerAnalysis; +import magpiebridge.core.MagpieServer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.gson.*; + +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; + +import org.zeroturnaround.exec.InvalidExitValueException; +import org.zeroturnaround.exec.ProcessExecutor; +import org.zeroturnaround.exec.ProcessResult; + +import goblintserver.GoblintClient; +import goblintserver.GoblintServer; +import goblintserver.Request; + + +public class GoblintAnalysis implements ServerAnalysis { + + private final MagpieServer magpieServer; + private final GoblintServer goblintServer; + private final GoblintClient goblintClient; + + private final Logger log = LogManager.getLogger(GoblintAnalysis.class); + + + public GoblintAnalysis(MagpieServer magpieServer, GoblintServer goblintServer, GoblintClient goblintClient) { + this.magpieServer = magpieServer; + this.goblintServer = goblintServer; + this.goblintClient = goblintClient; + } + + + /** + * The source of this analysis, usually the name of the analysis. + * + * @return the string + */ + + public String source() { + return "GobPie"; + } + + + /** + * The method that is triggered to start a new analysis. + * + * @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 files, AnalysisConsumer consumer, boolean rerun) { + if (rerun) { + if (consumer instanceof MagpieServer) { + + preAnalyse(); + + System.err.println("\n---------------------- Analysis started ----------------------"); + MagpieServer server = (MagpieServer) consumer; + Collection response = reanalyse(); + if (response != null) server.consume(new ArrayList<>(response), source()); + System.err.println("--------------------- Analysis finished ----------------------\n"); + + } + } + } + + + /** + * The method that is triggered before each analysis. + * + * preAnalyzeCommand is read from the GobPie configuration file. + * Can be used for automating the compilation database generation. + */ + + private void preAnalyse() { + String[] preAnalyzeCommand = goblintServer.getPreAnalyzeCommand(); + if (preAnalyzeCommand != null) { + try { + log.info("Preanalyze command ran: \"" + Arrays.toString(preAnalyzeCommand) + "\""); + runCommand(new File(System.getProperty("user.dir")), preAnalyzeCommand); + log.info("Preanalyze command finished."); + } catch (IOException | InvalidExitValueException | InterruptedException | TimeoutException e) { + this.magpieServer.forwardMessageToClient( + new MessageParams(MessageType.Warning, "Running preanalysis command failed. " + e.getMessage())); + } + } + } + + + /** + * Sends the request to Goblint server to reanalyse and reads the result. + * + * @return returns true if the request was sucessful, false otherwise + */ + + private Collection reanalyse() { + + // {"jsonrpc":"2.0","id":0,"method":"analyze","params":{}} + String analyzeRequest = new GsonBuilder().create().toJson(new Request("analyze")) + "\n"; + String messagesRequest = new GsonBuilder().create().toJson(new Request("messages")) + "\n"; + + try { + goblintClient.writeRequestToSocket(analyzeRequest); + goblintClient.readResponseFromSocket(); + goblintClient.writeRequestToSocket(messagesRequest); + JsonObject response = goblintClient.readResponseFromSocket(); + Collection results = convertResultsFromJson(response); + return results; + } catch (IOException e) { + log.info("Sending the request to or receiving result from the server failed: " + e); + e.printStackTrace(); + return null; + } + } + + + /** + * Method for running a command. + * + * @param dirPath The directory in which the command will run. + * @param command The command to run. + * @return Exit value and output of a finished process. + */ + + public ProcessResult runCommand(File dirPath, String[] command) throws IOException, InvalidExitValueException, InterruptedException, TimeoutException { + log.debug("Waiting for command: " + command.toString() + " to run..."); + ProcessResult process = new ProcessExecutor() + .directory(dirPath) + .command(command) + .redirectOutput(System.err) + .redirectError(System.err) + .execute(); + return process; + } + + + /** + * Deserializes json to GoblintResult objects and then converts the information + * into GoblintAnalysisResult objects, which Magpie uses to generate IDE + * messages. + * + * @return A collection of GoblintAnalysisResult objects. + */ + + private Collection convertResultsFromJson(JsonObject response) { + + Collection results = new ArrayList<>(); + + try { + log.debug("Reading analysis results from json."); + // Read json objects as an array + JsonArray messagesArray = response.get("result").getAsJsonArray(); + if (messagesArray != null && !messagesArray.isJsonArray()) { + log.error("Reading analysis results failed."); + this.magpieServer.forwardMessageToClient( + new MessageParams(MessageType.Error, "Reading analysis results failed.")); + return null; + } + GsonBuilder builder = new GsonBuilder(); + // Add deserializer for tags + builder.registerTypeAdapter(GoblintMessages.tag.class, new TagInterfaceAdapter()); + Gson gson = builder.create(); + + for (JsonElement msg : messagesArray) { + GoblintMessages goblintResult = gson.fromJson(msg, GoblintMessages.class); + results.addAll(goblintResult.convert()); + } + + log.debug("Analysis results read from json"); + + return results; + } catch (JsonIOException | JsonSyntaxException | MalformedURLException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/src/main/java/GoblintAnalysisResult.java b/src/main/java/analysis/GoblintAnalysisResult.java similarity index 99% rename from src/main/java/GoblintAnalysisResult.java rename to src/main/java/analysis/GoblintAnalysisResult.java index 8664daa..d050820 100644 --- a/src/main/java/GoblintAnalysisResult.java +++ b/src/main/java/analysis/GoblintAnalysisResult.java @@ -1,3 +1,5 @@ +package analysis; + import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.util.collections.Pair; diff --git a/src/main/java/GoblintResult.java b/src/main/java/analysis/GoblintMessages.java similarity index 69% rename from src/main/java/GoblintResult.java rename to src/main/java/analysis/GoblintMessages.java index 4b05259..9b14fab 100644 --- a/src/main/java/GoblintResult.java +++ b/src/main/java/analysis/GoblintMessages.java @@ -1,23 +1,16 @@ +package analysis; + import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.util.collections.Pair; import java.io.File; import java.net.MalformedURLException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Map.Entry; import java.util.stream.Collectors; -public class GoblintResult { - - private Map> files; - private List messages = new ArrayList<>(); - - static class Message { +public class GoblintMessages { private List tags = new ArrayList<>(); private String severity; @@ -78,26 +71,25 @@ static class loc { private int endColumn; } } - } } + public List convert() throws MalformedURLException { List results = new ArrayList<>(); - for (Message message : messages) { - if (message.multipiece.group_text == null) { - String msg = message.tags.stream().map(tag -> tag.toString()).collect(Collectors.joining("")) + " " + message.multipiece.text; - GoblintPosition pos = new GoblintPosition(message.multipiece.loc.line, message.multipiece.loc.endLine, message.multipiece.loc.column - 1, message.multipiece.loc.endColumn - 1, new File(message.multipiece.loc.file).toURI().toURL()); - GoblintAnalysisResult result = new GoblintAnalysisResult(pos, msg, message.severity); + if (multipiece.group_text == null) { + String msg = tags.stream().map(tag -> tag.toString()).collect(Collectors.joining("")) + " " + multipiece.text; + GoblintPosition pos = new GoblintPosition(multipiece.loc.line, multipiece.loc.endLine, multipiece.loc.column - 1, multipiece.loc.endColumn - 1, new File(multipiece.loc.file).toURI().toURL()); + GoblintAnalysisResult result = new GoblintAnalysisResult(pos, msg, severity); results.add(result); } else { List intermresults = new ArrayList<>(); - List pieces = message.multipiece.pieces; - for (Message.multipiece.pieces piece : pieces) { + List pieces = multipiece.pieces; + for (multipiece.pieces piece : pieces) { GoblintPosition pos = new GoblintPosition(piece.loc.line, piece.loc.endLine, piece.loc.column - 1, piece.loc.endColumn - 1, new File(piece.loc.file).toURI().toURL()); GoblintAnalysisResult result = new GoblintAnalysisResult(pos, - message.tags.stream().map(tag -> tag.toString()).collect(Collectors.joining("")) + " Group: " + message.multipiece.group_text, - piece.text, message.severity); + tags.stream().map(tag -> tag.toString()).collect(Collectors.joining("")) + " Group: " + multipiece.group_text, + piece.text, severity); intermresults.add(result); } // Add related warnings to all the group elements @@ -113,19 +105,18 @@ public List convert() throws MalformedURLException { res1.severityStr(), related)); } results.addAll(addedRelated); - } } return results; } - public List getFiles() { - Set allFiles = new HashSet<>(); - for (Entry> entry : files.entrySet()) { - allFiles.add(entry.getKey()); - allFiles.addAll(entry.getValue()); - } - return new ArrayList<>(allFiles); - } + // public List getFiles() { + // Set allFiles = new HashSet<>(); + // for (Entry> entry : files.entrySet()) { + // allFiles.add(entry.getKey()); + // allFiles.addAll(entry.getValue()); + // } + // return new ArrayList<>(allFiles); + // } } diff --git a/src/main/java/GoblintPosition.java b/src/main/java/analysis/GoblintPosition.java similarity index 98% rename from src/main/java/GoblintPosition.java rename to src/main/java/analysis/GoblintPosition.java index 09e2f8f..e70baf5 100644 --- a/src/main/java/GoblintPosition.java +++ b/src/main/java/analysis/GoblintPosition.java @@ -1,3 +1,5 @@ +package analysis; + import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.classLoader.IMethod; diff --git a/src/main/java/TagInterfaceAdapter.java b/src/main/java/analysis/TagInterfaceAdapter.java similarity index 88% rename from src/main/java/TagInterfaceAdapter.java rename to src/main/java/analysis/TagInterfaceAdapter.java index c5a59d1..4de8b71 100644 --- a/src/main/java/TagInterfaceAdapter.java +++ b/src/main/java/analysis/TagInterfaceAdapter.java @@ -1,3 +1,5 @@ +package analysis; + import java.lang.reflect.Type; import com.google.gson.JsonDeserializationContext; @@ -12,9 +14,9 @@ public class TagInterfaceAdapter implements JsonDeserializer { public Object deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); if (jsonObject.has("Category")) - return jsonDeserializationContext.deserialize(jsonObject, GoblintResult.Message.tag.Category.class); + return jsonDeserializationContext.deserialize(jsonObject, GoblintMessages.tag.Category.class); if (jsonObject.has("CWE")) - return jsonDeserializationContext.deserialize(jsonObject, GoblintResult.Message.tag.CWE.class); + return jsonDeserializationContext.deserialize(jsonObject, GoblintMessages.tag.CWE.class); return null; } diff --git a/src/main/java/GobPieConfiguration.java b/src/main/java/goblintserver/GobPieConfiguration.java similarity index 82% rename from src/main/java/GobPieConfiguration.java rename to src/main/java/goblintserver/GobPieConfiguration.java index 2685c5d..5d988cb 100644 --- a/src/main/java/GobPieConfiguration.java +++ b/src/main/java/goblintserver/GobPieConfiguration.java @@ -1,3 +1,5 @@ +package goblintserver; + public class GobPieConfiguration { private String goblintConf = ""; @@ -13,6 +15,7 @@ public String[] getFiles() { } public String[] getPreAnalyzeCommand() { + if (preAnalyzeCommand.length < 0) return null; return this.preAnalyzeCommand; } diff --git a/src/main/java/goblintserver/GoblintClient.java b/src/main/java/goblintserver/GoblintClient.java new file mode 100644 index 0000000..f07980e --- /dev/null +++ b/src/main/java/goblintserver/GoblintClient.java @@ -0,0 +1,90 @@ +package goblintserver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.Channels; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; + +import magpiebridge.core.MagpieServer; + + +public class GoblintClient { + + private MagpieServer magpieServer; + private SocketChannel channel; + private UnixDomainSocketAddress address; + private OutputStream outputStream; + private BufferedReader inputReader; + + private final String goblintSocketName = "goblint.sock"; + + private final Logger log = LogManager.getLogger(GoblintClient.class); + + + public GoblintClient(MagpieServer magpieServer) { + this.magpieServer = magpieServer; + } + + + /** + * Method for connecting to Goblint server socket. + * + * @return True if connection was started successfully, false otherwise. + */ + + public boolean connectGoblitClient() { + try { + // connect to the goblint socket + address = UnixDomainSocketAddress.of(Path.of(goblintSocketName)); + channel = SocketChannel.open(StandardProtocolFamily.UNIX); + boolean connected = channel.connect(address); + outputStream = Channels.newOutputStream(channel); + InputStream inputStream = Channels.newInputStream(channel); + inputReader = new BufferedReader(new InputStreamReader(inputStream)); + if (!connected) return false; + log.info("Goblint client connected."); + return true; + } catch (IOException e) { + this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Connecting GoblintClient failed. " + e.getMessage())); + return false; + } + } + + + /** + * Method for sending the requests to Goblint server. + */ + + public void writeRequestToSocket(String request) throws IOException { + outputStream.write(request.getBytes()); + log.info("Request written to socket."); + } + + + /** + * Method for reading the response from Goblint server. + */ + + public JsonObject readResponseFromSocket() throws IOException { + String response = inputReader.readLine(); + log.info("Response read from socket."); + JsonObject responseJson = new Gson().fromJson (response, JsonElement.class).getAsJsonObject(); + return responseJson; + } + +} diff --git a/src/main/java/goblintserver/GoblintServer.java b/src/main/java/goblintserver/GoblintServer.java new file mode 100644 index 0000000..351a382 --- /dev/null +++ b/src/main/java/goblintserver/GoblintServer.java @@ -0,0 +1,191 @@ +package goblintserver; + +import java.util.*; +import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; +import java.io.*; +import java.nio.file.*; + +import com.google.gson.*; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; +import org.zeroturnaround.exec.*; +import org.zeroturnaround.exec.listener.ProcessListener; + +import magpiebridge.core.MagpieServer; + + +public class GoblintServer { + + private MagpieServer magpieServer; + + private String gobPieConf = "gobpie.json"; + private String goblintSocket = "goblint.sock"; + + private String[] preAnalyzeCommand; + private String[] goblintRunCommand; + + private final Logger log = LogManager.getLogger(GoblintClient.class); + + + public GoblintServer(MagpieServer magpieServer) { + this.magpieServer = magpieServer; + } + + + public String[] getPreAnalyzeCommand() { + return preAnalyzeCommand; + } + + + /** + * Method to start the Goblint server. + * + * @return True if server was started successfully, false otherwise. + */ + + public boolean startGoblintServer() { + // read configuration file + boolean gobpieconf = readGobPieConfiguration(); + if (!gobpieconf) return false; + + try { + // run command to start goblint + log.info("Goblint run with command: " + String.join(" ", goblintRunCommand)); + + StartedProcess commandRunProcess = runCommand(new File(System.getProperty("user.dir")), goblintRunCommand); + + if (commandRunProcess.getFuture().isDone() && commandRunProcess.getProcess().exitValue() != 0) { + magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Goblint exited with an error.")); + log.error("Goblint exited with an error."); + return false; + } + + // wait until Goblint socket is created before continuing + WatchService watchService = FileSystems.getDefault().newWatchService(); + Path path = Paths.get(System.getProperty("user.dir")); + path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE ); + WatchKey key; + while ((key = watchService.take()) != null) { + for (WatchEvent event : key.pollEvents()) { + + if (((Path) event.context()).equals(Paths.get(goblintSocket))) { + log.info("Goblint server started."); + return true; + } + } + key.reset(); + } + + return false; + + } catch (IOException | InvalidExitValueException | InterruptedException | TimeoutException e) { + this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Running Goblint failed. " + e.getMessage())); + return false; + } + } + + + // public void stopGoblintServer() { + // try { + // Files.deleteIfExists(socketPath); + // } catch (IOException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + // } + + + /** + * Method for running a command. + * + * @param dirPath The directory in which the command will run. + * @param command The command to run. + * @return An object that represents a process that has started. It may or may not have finished. + */ + + public StartedProcess runCommand(File dirPath, String[] command) throws IOException, InterruptedException, InvalidExitValueException, TimeoutException { + ProcessListener listener = new ProcessListener() { + public void afterFinish(Process process, ProcessResult result) { + magpieServer.forwardMessageToClient(new MessageParams(MessageType.Info, "Goblint server finished.")); + log.info("Goblint server finished."); + } + + public void afterStop(Process process) { + if (process.exitValue() != 0) { + magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Goblint server exited due to an error.")); + log.error("Goblint server exited due to an error."); + } else { + magpieServer.forwardMessageToClient(new MessageParams(MessageType.Info, "Goblint server has stopped.")); + log.info("Goblint server has stopped."); + } + } + }; + + log.debug("Waiting for command: " + command.toString() + " to run..."); + StartedProcess process = new ProcessExecutor() + .directory(dirPath) + .command(command) + .redirectOutput(System.err) + .redirectError(System.err) + .addListener(listener) + .start(); + return process; + } + + + /** + * Method for reading GobPie configuration. + * Deserializes json to GobPieConfiguration object. + * + * @return true if gobpie configuration was read sucessfully, false otherwise: + * * no goblint configuration file was specified; + * * no files to analyse have been listed; + * * no gobpie.json file is found in root directory + */ + + 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.preAnalyzeCommand = gobpieConfiguration.getPreAnalyzeCommand(); + + // Check if all required parameters have been set + if (gobpieConfiguration.getGoblintConf().equals("") || gobpieConfiguration.getFiles() == null || gobpieConfiguration.getFiles().length < 1) { + log.debug("Configuration parameters missing from GobPie configuration file"); + this.magpieServer.forwardMessageToClient(new MessageParams(MessageType.Error, "Configuration parameters missing from GobPie configuration file.")); + return false; + } + + // Construct command to run Goblint Server + // by concatenating the run command with files to analyse (read from GobPie conf) + this.goblintRunCommand = Stream.concat( + Arrays.stream(new String[]{"goblint", "--conf", new File(gobpieConfiguration.getGoblintConf()).getAbsolutePath(), + "--enable", "server.enabled", + "--enable", "server.reparse", + "--set", "server.mode", "unix", + "--set", "server.unix-socket", new File(goblintSocket).getAbsolutePath()}), + Arrays.stream(gobpieConfiguration.getFiles())) + .toArray(String[]::new); + + 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; + } + +} diff --git a/src/main/java/goblintserver/Request.java b/src/main/java/goblintserver/Request.java new file mode 100644 index 0000000..6e955b0 --- /dev/null +++ b/src/main/java/goblintserver/Request.java @@ -0,0 +1,37 @@ +package goblintserver; + +public class Request { + // // {"jsonrpc":"2.0","id":0,"method":"analyze","params":{}} + + private String jsonrpc = "2.0"; + private int id = 0; + private String method; + private params params; + + static class params { + } + + public Request(String method) { + this.method = method; + if (method == "analyze") { + this.params = new params(); + } + } + + public String getJsonrpc() { + return jsonrpc; + } + + public int getId() { + return id; + } + + public String getMethod() { + return method; + } + + public params getParams() { + return params; + } + +} diff --git a/vscode/package.json b/vscode/package.json index 70d9180..058b6b8 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1,9 +1,9 @@ { - "name": "goblintanalyzer", - "description": "The Goblint analyzer", + "name": "gobpie", + "description": "The Goblint analyzer interactive extension", "author": "Karoliine Holter", "license": "EPL-2.0", - "version": "0.0.1", + "version": "0.0.2", "repository": { "type": "git", "url": "https://github.com/karoliineh/MagpieBridge-Goblint.git" @@ -23,9 +23,9 @@ "contributes": { "configuration": { "type": "object", - "title": "GoblintAnalyzer", + "title": "GobPie", "properties": { - "GoblintAnalyzer.trace.server": { + "gobpie.trace.server": { "scope": "window", "type": "string", "enum": [ @@ -52,7 +52,7 @@ ] }, "scripts": { - "vscode:prepublish": "cp ../target/goblintanalyzer-0.0.1-SNAPSHOT.jar goblintanalyzer-0.0.1-SNAPSHOT.jar && npm run compile", + "vscode:prepublish": "cp ../target/gobpie-0.0.2-SNAPSHOT.jar gobpie-0.0.2-SNAPSHOT.jar && npm run compile", "compile": "tsc -b", "watch": "tsc -b -w", "postinstall": "node ./node_modules/vscode/bin/install" diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index a40e7d7..71831b4 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -7,7 +7,7 @@ import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from export function activate(context: ExtensionContext) { let script = 'java'; - let args = ['-jar',context.asAbsolutePath('goblintanalyzer-0.0.1-SNAPSHOT.jar')]; + let args = ['-jar',context.asAbsolutePath('gobpie-0.0.2-SNAPSHOT.jar')]; // Use this for communicating on stdio let serverOptions: ServerOptions = { @@ -41,7 +41,7 @@ export function activate(context: ExtensionContext) { }; // Create the language client and start the client. - let lc : LanguageClient = new LanguageClient('GoblintAnalyzer','GoblintAnalyzer', serverOptions, clientOptions); + let lc : LanguageClient = new LanguageClient('GobPie','GobPie', serverOptions, clientOptions); lc.start(); }