Skip to content

Commit

Permalink
Merge pull request #36 from goblint/show-cfg
Browse files Browse the repository at this point in the history
Show CFGs for functions in the IDE
  • Loading branch information
karoliineh authored Jul 26, 2022
2 parents 95adf7a + 4c84439 commit 3b577f0
Show file tree
Hide file tree
Showing 19 changed files with 661 additions and 197 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
distribution: 'adopt'
cache: maven

- name: Set up Node.js 14
- name: Set up Node.js 18
uses: actions/setup-node@v2
with:
node-version: '14.x' # TODO: what version do we want?
node-version: '18.x' # TODO: what version do we want?
# cache: npm # TODO: requires package-lock.json

- name: Build with Maven
Expand Down
13 changes: 10 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,28 @@
<modelVersion>4.0.0</modelVersion>
<groupId>goblint</groupId>
<artifactId>gobpie</artifactId>
<version>0.0.2-SNAPSHOT</version>
<version>0.0.3-SNAPSHOT</version>
<dependencies>

<!-- magpiebridge -->
<dependency>
<groupId>com.github.magpiebridge</groupId>
<artifactId>magpiebridge</artifactId>
<version>0.1.3</version>
<version>0.1.5</version>
</dependency>

<!-- graphviz-java -->
<dependency>
<groupId>guru.nidi</groupId>
<artifactId>graphviz-java</artifactId>
<version>0.18.1</version>
</dependency>

<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
<version>2.9.0</version>
</dependency>

<!-- zt-exec -->
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.IOException;
import java.nio.file.*;

import analysis.ShowCFGCommand;
import magpiebridge.core.MagpieServer;
import magpiebridge.core.ServerAnalysis;
import magpiebridge.core.ServerConfiguration;
Expand All @@ -27,6 +28,7 @@
public class Main {

private static final String gobPieConfFileName = "gobpie.json";
private static final String cfgHttpServer = "http://localhost:8080/";
private static final Logger log = LogManager.getLogger(Main.class);
private static MagpieServer magpieServer;

Expand All @@ -35,6 +37,9 @@ public static void main(String... args) {
try {
createMagpieServer();
addAnalysis();
// launch magpieServer
magpieServer.launchOnStdio();
log.info("MagpieBridge server launched.");
magpieServer.doAnalysis("c", true);
} catch (GobPieException e) {
String message = e.getMessage();
Expand All @@ -60,22 +65,15 @@ public static void main(String... args) {
*/

private static void createMagpieServer() {

// set up configuration for MagpieServer
ServerConfiguration serverConfig = new ServerConfiguration();
serverConfig.setDoAnalysisByFirstOpen(false);
magpieServer = new MagpieServer(serverConfig);

// launch magpieServer
magpieServer.launchOnStdio();
log.info("MagpieBridge server launched.");

}


/**
* Method for creating and adding GoblintAnalysis to MagpieBridge server
* and doing the initial analysis.
* Method for creating and adding Goblint analysis to MagpieBridge server.
* <p>
* Creates GoblintServer, GoblintClient and the GoblintAnalysis classes.
*
Expand Down Expand Up @@ -105,6 +103,10 @@ private static void addAnalysis() {
ServerAnalysis serverAnalysis = new GoblintAnalysis(magpieServer, goblintServer, goblintClient, gobpieConfiguration);
Either<ServerAnalysis, ToolAnalysis> analysis = Either.forLeft(serverAnalysis);
magpieServer.addAnalysis(analysis, language);

// add HTTP server for showing CFGs
magpieServer.addHttpServer(cfgHttpServer);
magpieServer.addCommand("showcfg", new ShowCFGCommand(goblintClient));
}


Expand Down
96 changes: 62 additions & 34 deletions src/main/java/analysis/GoblintAnalysis.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import com.ibm.wala.classLoader.Module;
import goblintclient.GoblintClient;
import goblintclient.communication.AnalyzeResponse;
import goblintclient.communication.MessagesResponse;
import goblintclient.communication.Request;
import goblintclient.communication.*;
import goblintclient.messages.GoblintFunctions;
import goblintclient.messages.GoblintMessages;
import goblintserver.GoblintServer;
import gobpie.GobPieConfiguration;
import gobpie.GobPieException;
import gobpie.GobPieExceptionType;
import magpiebridge.core.AnalysisConsumer;
import magpiebridge.core.AnalysisResult;
import magpiebridge.core.MagpieServer;
import magpiebridge.core.ServerAnalysis;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
Expand All @@ -27,7 +27,6 @@
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -37,7 +36,20 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* The Class GoblintAnalysis.
* <p>
* 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 {

Expand Down Expand Up @@ -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
Expand All @@ -92,7 +104,7 @@ public void analyze(Collection<? extends Module> files, AnalysisConsumer consume
preAnalyse();
log.info("---------------------- Analysis started ----------------------");
Runnable analysisTask = () -> {
Collection<GoblintAnalysisResult> response = reanalyse();
Collection<AnalysisResult> response = reanalyse();
if (response != null) {
server.consume(new ArrayList<>(response), source());
log.info("--------------------- Analysis finished ----------------------");
Expand Down Expand Up @@ -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<GoblintAnalysisResult> reanalyse() {
private Collection<AnalysisResult> 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.
Expand All @@ -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<GoblintAnalysisResult> convertResultsFromJson(MessagesResponse messagesResponse) {
private Collection<AnalysisResult> convertResultsFromJson(MessagesResponse response) {
return response.getResult().stream().map(GoblintMessages::convert).flatMap(List::stream).collect(Collectors.toList());
}

Collection<GoblintAnalysisResult> results = new ArrayList<>();
try {
List<GoblintMessages> messagesArray = messagesResponse.getResult();
for (GoblintMessages msg : messagesArray) {
results.addAll(msg.convert());
}
return results;
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
private Collection<AnalysisResult> 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.
*/
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/analysis/GoblintCFGAnalysisResult.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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<Pair<CAstSourcePositionMap.Position, String>> 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 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<Pair<CAstSourcePositionMap.Position, String>> related() {
return related;
}

@Override
public DiagnosticSeverity severity() {
return DiagnosticSeverity.Information;
}

@Override
public Pair<CAstSourcePositionMap.Position, String> repair() {
return null;
}

@Override
public String code() {
return null;
}
}
Loading

0 comments on commit 3b577f0

Please sign in to comment.