+ * Implementation of the ServerAnalysis interface.
+ * The class that is responsible for analyzing when an analysis event is triggered.
+ * Sends the requests to Goblint server, reads the corresponding responses,
+ * converts the results and passes them to MagpieBridge server.
+ *
+ * @author Karoliine Holter
+ * @since 0.0.1
+ */
public class GoblintAnalysis implements ServerAnalysis {
@@ -79,7 +91,7 @@ public String source() {
*
* @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.
+ * @param rerun tells if the analysis should be rerun.
*/
@Override
@@ -92,7 +104,7 @@ public void analyze(Collection extends Module> files, AnalysisConsumer consume
preAnalyse();
log.info("---------------------- Analysis started ----------------------");
Runnable analysisTask = () -> {
- Collection response = reanalyse();
+ Collection response = reanalyse();
if (response != null) {
server.consume(new ArrayList<>(response), source());
log.info("--------------------- Analysis finished ----------------------");
@@ -142,35 +154,56 @@ private void abortAnalysis() {
/**
- * Sends the request to Goblint server to reanalyse and reads the result.
+ * Sends the requests to Goblint server and reads their results.
*
- * @return returns a collection of analysis results if the request was sucessful, null otherwise
+ * @return a collection of warning messages and cfg code lenses if request was successful, null otherwise.
*/
- private Collection reanalyse() {
+ private Collection reanalyse() {
Request analyzeRequest = new Request("analyze");
Request messagesRequest = new Request("messages");
+ Request functionsRequest = new Request("functions");
try {
+ // Analyze
analysisRunning.set(true);
- goblintClient.writeRequestToSocket(analyzeRequest);
- AnalyzeResponse analyzeResponse = goblintClient.readAnalyzeResponseFromSocket();
+ AnalyzeResponse analyzeResponse = (AnalyzeResponse) getResponse(analyzeRequest);
analysisRunning.set(false);
- if (!analyzeRequest.getId().equals(analyzeResponse.getId()))
- throw new GobPieException("Response ID does not match request ID.", GobPieExceptionType.GOBLINT_EXCEPTION);
if (analyzeResponse.getResult().getStatus().contains("Aborted"))
return null;
- goblintClient.writeRequestToSocket(messagesRequest);
- MessagesResponse messagesResponse = goblintClient.readMessagesResponseFromSocket();
- if (!messagesRequest.getId().equals(messagesResponse.getId()))
- throw new GobPieException("Response ID does not match request ID.", GobPieExceptionType.GOBLINT_EXCEPTION);
- return convertResultsFromJson(messagesResponse);
+ // Get warning messages
+ MessagesResponse messagesResponse = (MessagesResponse) getResponse(messagesRequest);
+ // Get list of functions
+ FunctionsResponse functionsResponse = (FunctionsResponse) getResponse(functionsRequest);
+ return Stream.concat(convertResultsFromJson(messagesResponse).stream(), convertResultsFromJson(functionsResponse).stream()).collect(Collectors.toList());
} catch (IOException e) {
throw new GobPieException("Sending the request to or receiving result from the server failed.", e, GobPieExceptionType.GOBLINT_EXCEPTION);
}
}
+ /**
+ * Writes the request to the socket and reads its response according to the request that was sent.
+ *
+ * @param request The request to be written into socket.
+ * @return the response to the request that was sent.
+ * @throws GobPieException if the request and response ID do not match.
+ */
+
+ private Response getResponse(Request request) throws IOException {
+ goblintClient.writeRequestToSocket(request);
+ Response response;
+ if (request.getMethod().equals("analyze"))
+ response = goblintClient.readAnalyzeResponseFromSocket();
+ else if (request.getMethod().equals("messages"))
+ response = goblintClient.readMessagesResponseFromSocket();
+ else
+ response = goblintClient.readFunctionsResponseFromSocket();
+ if (!request.getId().equals(response.getId()))
+ throw new GobPieException("Response ID does not match request ID.", GobPieExceptionType.GOBLINT_EXCEPTION);
+ return response;
+ }
+
/**
* Method for running a command.
@@ -192,31 +225,26 @@ public ProcessResult runCommand(File dirPath, String[] command) throws IOExcepti
/**
- * Deserializes json to GoblintResult objects and then converts the information
- * into GoblintAnalysisResult objects, which Magpie uses to generate IDE
- * messages.
+ * Deserializes json from the response and converts the information
+ * into AnalysisResult objects, which Magpie uses to generate IDE messages.
*
- * @return A collection of GoblintAnalysisResult objects.
+ * @param response that was read from the socket and needs to be converted to AnalysisResults.
+ * @return A collection of AnalysisResult objects.
*/
- private Collection convertResultsFromJson(MessagesResponse messagesResponse) {
+ private Collection convertResultsFromJson(MessagesResponse response) {
+ return response.getResult().stream().map(GoblintMessages::convert).flatMap(List::stream).collect(Collectors.toList());
+ }
- Collection results = new ArrayList<>();
- try {
- List messagesArray = messagesResponse.getResult();
- for (GoblintMessages msg : messagesArray) {
- results.addAll(msg.convert());
- }
- return results;
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
- }
+ private Collection convertResultsFromJson(FunctionsResponse response) {
+ return response.getResult().stream().map(GoblintFunctions::convert).collect(Collectors.toList());
}
/**
* Method for creating an observer for Goblint configuration file.
* So that the server could be restarted when the configuration file is changed.
+ * TODO: instead of restarting the server, send new configuration with a request to the server #32
*
* @return The FileAlterationObserver of project root directory.
*/
diff --git a/src/main/java/analysis/GoblintCFGAnalysisResult.java b/src/main/java/analysis/GoblintCFGAnalysisResult.java
new file mode 100644
index 0000000..fb05d5d
--- /dev/null
+++ b/src/main/java/analysis/GoblintCFGAnalysisResult.java
@@ -0,0 +1,75 @@
+package analysis;
+
+import com.ibm.wala.cast.tree.CAstSourcePositionMap;
+import com.ibm.wala.util.collections.Pair;
+import magpiebridge.core.AnalysisResult;
+import magpiebridge.core.Kind;
+import org.eclipse.lsp4j.Command;
+import org.eclipse.lsp4j.DiagnosticSeverity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * The Class GoblintCFGAnalysisResult.
+ *
+ * Implementation of the GoblintAnalysisResult class that extends MagpieBridge AnalysisResult class.
+ * The class that corresponds to the CFG code lenses.
+ *
+ * @author Karoliine Holter
+ * @since 0.0.3
+ */
+
+public class GoblintCFGAnalysisResult implements AnalysisResult {
+ private final CAstSourcePositionMap.Position pos;
+ private final String funName;
+ private final String fileName;
+ private final Iterable> related = new ArrayList<>();
+
+ public GoblintCFGAnalysisResult(CAstSourcePositionMap.Position pos, String funName, String fileName) {
+ this.pos = pos;
+ this.funName = funName;
+ this.fileName = fileName;
+ }
+
+ @Override
+ public Iterable command() {
+ Command command = new Command("show cfg", "showcfg", Collections.singletonList(funName));
+ return Collections.singleton(command);
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.CodeLens;
+ }
+
+ @Override
+ public String toString(boolean useMarkdown) {
+ return "cfg";
+ }
+
+ @Override
+ public CAstSourcePositionMap.Position position() {
+ return pos;
+ }
+
+ @Override
+ public Iterable> related() {
+ return related;
+ }
+
+ @Override
+ public DiagnosticSeverity severity() {
+ return DiagnosticSeverity.Information;
+ }
+
+ @Override
+ public Pair repair() {
+ return null;
+ }
+
+ @Override
+ public String code() {
+ return null;
+ }
+}
diff --git a/src/main/java/analysis/GoblintAnalysisResult.java b/src/main/java/analysis/GoblintMessagesAnalysisResult.java
similarity index 77%
rename from src/main/java/analysis/GoblintAnalysisResult.java
rename to src/main/java/analysis/GoblintMessagesAnalysisResult.java
index f93e1e4..bc477f0 100644
--- a/src/main/java/analysis/GoblintAnalysisResult.java
+++ b/src/main/java/analysis/GoblintMessagesAnalysisResult.java
@@ -2,27 +2,23 @@
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.util.collections.Pair;
-
-import java.util.ArrayList;
-
+import goblintclient.messages.GoblintPosition;
import magpiebridge.core.AnalysisResult;
import magpiebridge.core.Kind;
-// import magpiebridge.util.SourceCodeReader;
-
import org.eclipse.lsp4j.DiagnosticSeverity;
-import goblintclient.messages.GoblintPosition;
+import java.util.ArrayList;
/**
- * The Class GoblintAnalysisResult.
+ * The Class GoblintMessagesAnalysisResult.
*
- * Implementation of the MagpieBridge AnalysisResult class.
- * Customizes it for the needs of Goblint.
+ * Implementation of the GoblintAnalysisResult class that extends MagpieBridge AnalysisResult class.
+ * The class that corresponds to the Goblint warnings that are shown in the IDE.
*
- * @author Julian Dolby, Linghui Luo and Karoliine Holter
+ * @author Karoliine Holter
*/
-public class GoblintAnalysisResult implements AnalysisResult {
+public class GoblintMessagesAnalysisResult implements AnalysisResult {
private String group_text = "";
private final String text;
@@ -30,13 +26,13 @@ public class GoblintAnalysisResult implements AnalysisResult {
private final String severity;
private Iterable> related = new ArrayList<>();
- public GoblintAnalysisResult(GoblintPosition pos, String text, String severity) {
+ public GoblintMessagesAnalysisResult(GoblintPosition pos, String text, String severity) {
this.text = text;
this.pos = pos;
this.severity = severity;
}
- public GoblintAnalysisResult(GoblintPosition pos, String group_text, String text, String severity) {
+ public GoblintMessagesAnalysisResult(GoblintPosition pos, String group_text, String text, String severity) {
this.group_text = group_text;
this.text = text;
this.pos = pos;
@@ -44,7 +40,7 @@ public GoblintAnalysisResult(GoblintPosition pos, String group_text, String text
}
- public GoblintAnalysisResult(Position pos, String text, String severity, Iterable> related) {
+ public GoblintMessagesAnalysisResult(Position pos, String text, String severity, Iterable> related) {
this.text = text;
this.pos = pos;
this.severity = severity;
diff --git a/src/main/java/analysis/ShowCFGCommand.java b/src/main/java/analysis/ShowCFGCommand.java
new file mode 100644
index 0000000..ba8ccf0
--- /dev/null
+++ b/src/main/java/analysis/ShowCFGCommand.java
@@ -0,0 +1,117 @@
+package analysis;
+
+import com.google.gson.JsonPrimitive;
+import goblintclient.GoblintClient;
+import goblintclient.communication.CFGResponse;
+import goblintclient.communication.Request;
+import gobpie.GobPieException;
+import gobpie.GobPieExceptionType;
+import guru.nidi.graphviz.engine.Format;
+import guru.nidi.graphviz.engine.Graphviz;
+import magpiebridge.core.MagpieClient;
+import magpiebridge.core.MagpieServer;
+import magpiebridge.core.WorkspaceCommand;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.eclipse.lsp4j.ExecuteCommandParams;
+import org.eclipse.lsp4j.services.LanguageClient;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+public class ShowCFGCommand implements WorkspaceCommand {
+
+ private final GoblintClient goblintClient;
+ private final Logger log = LogManager.getLogger(ShowCFGCommand.class);
+
+ public ShowCFGCommand(GoblintClient goblintClient) {
+ this.goblintClient = goblintClient;
+ }
+
+ @Override
+ public void execute(ExecuteCommandParams params, MagpieServer server, LanguageClient client) {
+ try {
+ String funName;
+ Object uriJson = params.getArguments().get(0);
+ if (uriJson instanceof JsonPrimitive) {
+ funName = ((JsonPrimitive) uriJson).getAsString();
+ } else {
+ funName = (String) uriJson;
+ }
+ log.info("Showing CFG for function: " + funName);
+ String cfg = getCFG(funName);
+ showHTMLinClientOrBrowser(server, client, cfg);
+ } catch (IOException | URISyntaxException e) {
+ MagpieServer.ExceptionLogger.log(e);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Writes the request to the socket to get the cfg for the given function.
+ *
+ * @param funName The function name for which the CFG was requested.
+ * @return the CFG of the given function as a dot language string.
+ * @throws GobPieException if the request and response ID do not match.
+ */
+
+ public String getCFG(String funName) {
+ Request cfgRequest = new Request("cfg", funName);
+ try {
+ goblintClient.writeRequestToSocket(cfgRequest);
+ CFGResponse cfgResponse = goblintClient.readCFGResponseFromSocket();
+ if (!cfgRequest.getId().equals(cfgResponse.getId()))
+ throw new GobPieException("Response ID does not match request ID.", GobPieExceptionType.GOBLINT_EXCEPTION);
+ return cfgResponse.getResult().getCfg();
+ } catch (IOException e) {
+ throw new GobPieException("Sending the request to or receiving result from the server failed.", e, GobPieExceptionType.GOBLINT_EXCEPTION);
+ }
+ }
+
+
+ /**
+ * Show A HTML page with the given CFG in the client, or in a browser if the client doesn't support this.
+ *
+ * @param server The MagpieServer
+ * @param client The IDE/Editor
+ * @param cfg The CFG which should be shown
+ * @throws IOException IO exception
+ * @throws URISyntaxException URI exception
+ */
+
+ public static void showHTMLinClientOrBrowser(MagpieServer server, LanguageClient client, String cfg) throws IOException, URISyntaxException {
+ if (server.clientSupportShowHTML()) {
+ if (client instanceof MagpieClient) {
+ // Generate svg from dot using graphviz-java
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ Graphviz.fromString(cfg).render(Format.SVG).toOutputStream(output);
+ String svg = output.toString();
+ // TODO: improve this HTML horror?
+ String content =
+ "\n" +
+ "\n" +
+ " \n" +
+ " \n" +
+ " Preview\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ /*" " +*/
+ " " +
+ " \n" +
+ "";
+ ((MagpieClient) client).showHTML(content);
+ }
+ } /*else {
+ // TODO: Not tested if this works, probably not?
+ if (Desktop.isDesktopSupported())
+ Desktop.getDesktop().browse(new URI(URIUtils.checkURI(cfg)));
+ }*/
+ }
+}
+
diff --git a/src/main/java/goblintclient/GoblintClient.java b/src/main/java/goblintclient/GoblintClient.java
index 96ebfa4..0c1e0ba 100644
--- a/src/main/java/goblintclient/GoblintClient.java
+++ b/src/main/java/goblintclient/GoblintClient.java
@@ -1,32 +1,25 @@
package goblintclient;
-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.GsonBuilder;
-
-import goblintclient.communication.Request;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import goblintclient.communication.AnalyzeResponse;
-import goblintclient.communication.MessagesResponse;
+import goblintclient.communication.*;
import goblintclient.messages.GoblintMessages;
import goblintclient.messages.GoblintTagInterfaceAdapter;
import gobpie.GobPieException;
import gobpie.GobPieExceptionType;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.*;
+import java.net.StandardProtocolFamily;
+import java.net.UnixDomainSocketAddress;
+import java.nio.channels.Channels;
+import java.nio.channels.SocketChannel;
+import java.nio.file.Path;
/**
+ * s
* The Class GoblintClient.
*
* Handles the communication with Goblint Server through unix socket.
@@ -85,7 +78,7 @@ public void writeRequestToSocket(Request request) throws IOException {
/**
- * Method for reading the response from Goblint server.
+ * Methods for reading the responses from Goblint server.
*
* @return JsonObject of the results read from Goblint socket.
*/
@@ -98,12 +91,6 @@ public AnalyzeResponse readAnalyzeResponseFromSocket() throws IOException {
}
- /**
- * Method for reading the response from Goblint server.
- *
- * @return JsonObject of the results read from Goblint socket.
- */
-
public MessagesResponse readMessagesResponseFromSocket() throws IOException {
String response = inputReader.readLine();
GsonBuilder builder = new GsonBuilder();
@@ -116,4 +103,20 @@ public MessagesResponse readMessagesResponseFromSocket() throws IOException {
}
+ public FunctionsResponse readFunctionsResponseFromSocket() throws IOException {
+ String response = inputReader.readLine();
+ FunctionsResponse functionsResponse = new Gson().fromJson(response, FunctionsResponse.class);
+ log.info("Response " + functionsResponse.getId() + " read from socket.");
+ return functionsResponse;
+ }
+
+
+ public CFGResponse readCFGResponseFromSocket() throws IOException {
+ String response = inputReader.readLine();
+ CFGResponse cfgResponse = new Gson().fromJson(response, CFGResponse.class);
+ log.info("Response " + cfgResponse.getId() + " read from socket.");
+ return cfgResponse;
+ }
+
+
}
diff --git a/src/main/java/goblintclient/communication/CFGResponse.java b/src/main/java/goblintclient/communication/CFGResponse.java
new file mode 100644
index 0000000..fc6edb1
--- /dev/null
+++ b/src/main/java/goblintclient/communication/CFGResponse.java
@@ -0,0 +1,24 @@
+package goblintclient.communication;
+
+import goblintclient.messages.GoblintCFG;
+
+/**
+ * The Class CFGResponse.
+ *