From b6f7523b6fb5d1bb8bcdf0d8acc48e61a4a4e282 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Thu, 16 Jun 2022 12:14:24 +0300 Subject: [PATCH 01/11] Add initial cfg showing codelens and update dependencies --- pom.xml | 2 +- src/main/java/Main.java | 5 + .../java/analysis/GoblintAnalysisResult.java | 14 ++ src/main/java/analysis/ShowCFGCommand.java | 73 +++++++++ vscode/package.json | 139 +++++++++--------- vscode/src/extension.ts | 97 +++++++++--- 6 files changed, 243 insertions(+), 87 deletions(-) create mode 100644 src/main/java/analysis/ShowCFGCommand.java diff --git a/pom.xml b/pom.xml index 94d16f3..9ed561e 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.github.magpiebridge magpiebridge - 0.1.3 + 0.1.5 diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 752cea8..eb13fe5 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -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; @@ -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; @@ -66,6 +68,9 @@ private static void createMagpieServer() { serverConfig.setDoAnalysisByFirstOpen(false); magpieServer = new MagpieServer(serverConfig); + magpieServer.addHttpServer(cfgHttpServer); + magpieServer.addCommand("showcfg", new ShowCFGCommand()); + // launch magpieServer magpieServer.launchOnStdio(); log.info("MagpieBridge server launched."); diff --git a/src/main/java/analysis/GoblintAnalysisResult.java b/src/main/java/analysis/GoblintAnalysisResult.java index f93e1e4..ea6ce44 100644 --- a/src/main/java/analysis/GoblintAnalysisResult.java +++ b/src/main/java/analysis/GoblintAnalysisResult.java @@ -4,11 +4,17 @@ import com.ibm.wala.util.collections.Pair; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import magpiebridge.command.CodeActionCommand; import magpiebridge.core.AnalysisResult; import magpiebridge.core.Kind; // import magpiebridge.util.SourceCodeReader; +import magpiebridge.util.SourceCodePositionUtils; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import goblintclient.messages.GoblintPosition; @@ -112,4 +118,12 @@ public String code() { return null; } + @Override + public Iterable command() { + // TODO: hardcoded + String cfgPath = "http://localhost:8080/cfgs/src%252Fexample.c/main.svg"; + Command command = new Command("show cfg", "showcfg", Collections.singletonList(cfgPath)); + return Collections.singleton(command); + } + } diff --git a/src/main/java/analysis/ShowCFGCommand.java b/src/main/java/analysis/ShowCFGCommand.java new file mode 100644 index 0000000..9d1982d --- /dev/null +++ b/src/main/java/analysis/ShowCFGCommand.java @@ -0,0 +1,73 @@ +package analysis; + +import com.google.gson.JsonPrimitive; +import magpiebridge.core.MagpieClient; +import magpiebridge.core.MagpieServer; +import magpiebridge.core.WorkspaceCommand; +import magpiebridge.util.URIUtils; +import org.eclipse.lsp4j.ExecuteCommandParams; +import org.eclipse.lsp4j.services.LanguageClient; + +import java.awt.*; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +public class ShowCFGCommand implements WorkspaceCommand { + + @Override + public void execute(ExecuteCommandParams params, MagpieServer server, LanguageClient client) { + try { + String uri; + Object uriJson = params.getArguments().get(0); + if (uriJson instanceof JsonPrimitive) { + uri = ((JsonPrimitive) uriJson).getAsString(); + } else { + uri = (String) uriJson; + } + showHTMLinClientOrBrowser(server, client, uri); + } catch (IOException | URISyntaxException e) { + MagpieServer.ExceptionLogger.log(e); + e.printStackTrace(); + } + } + + + /** + * 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) { + String content = + "\n" + + "\n" + + " \n" + + " \n" + + " Preview\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + ((MagpieClient) client).showHTML(content); + } + } else { + // TODO: probably does not work? + if (Desktop.isDesktopSupported()) + Desktop.getDesktop().browse(new URI(URIUtils.checkURI(cfg))); + } + } +} + diff --git a/vscode/package.json b/vscode/package.json index e9df7cf..438ddab 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1,71 +1,72 @@ { - "name": "gobpie", - "displayName": "GobPie", - "description": "The interactive Goblint analyzer extension", - "author": "Karoliine Holter", - "license": "EPL-2.0", - "version": "0.0.2", - "repository": { - "type": "git", - "url": "https://github.com/karoliineh/MagpieBridge-Goblint.git" - }, - "publisher": "karoliineh", - "categories": [], - "keywords": [ - "multi-root ready" - ], - "engines": { - "vscode": "^1.30.0" - }, - "activationEvents": [ - "onLanguage:c" - ], - "main": "./out/extension", - "contributes": { - "configuration": { - "type": "object", - "title": "GobPie", - "properties": { - "gobpie.trace.server": { - "scope": "window", - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VS Code and the language server." - } - } - }, - "languages": [ - { - "id": "c", - "aliases": [ - "C", - "c" - ], - "extensions": [ - ".c" - ] - } - ] - }, - "scripts": { - "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" - }, - "dependencies": { - "vscode-languageclient": "^5.2.1" - }, - "devDependencies": { - "vscode": "^1.1.33", - "@types/mocha": "^5.2.6", - "@types/node": "^11.13.0", - "tslint": "^5.15.0", - "typescript": "^3.4.2" - } + "name": "gobpie", + "displayName": "GobPie", + "description": "The interactive Goblint analyzer extension", + "author": "Karoliine Holter", + "license": "EPL-2.0", + "version": "0.0.2", + "repository": { + "type": "git", + "url": "https://github.com/karoliineh/MagpieBridge-Goblint.git" + }, + "publisher": "karoliineh", + "categories": [], + "keywords": [ + "multi-root ready" + ], + "engines": { + "vscode": "^1.30.0" + }, + "activationEvents": [ + "onLanguage:c" + ], + "main": "./out/extension", + "contributes": { + "configuration": { + "type": "object", + "title": "GobPie", + "properties": { + "gobpie.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "Traces the communication between VS Code and the language server." + } + } + }, + "languages": [ + { + "id": "c", + "aliases": [ + "C", + "c" + ], + "extensions": [ + ".c" + ] + } + ] + }, + "scripts": { + "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" + }, + "dependencies": { + "vscode-languageclient": "^5.2.1", + "xmlhttprequest-ts": "^1.0.1" + }, + "devDependencies": { + "@vscode/test-electron": "^2.1.4", + "@types/vscode": "^1.1.37", + "@types/mocha": "^9.1.1", + "@types/node": "^11.13.0", + "tslint": "^5.15.0", + "typescript": "^3.4.2" + } } diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 71831b4..7871250 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -1,23 +1,33 @@ 'use strict'; -import * as net from 'net'; -import * as path from 'path'; +import {ExtensionContext, ViewColumn, window, workspace} from 'vscode'; +import { + ClientCapabilities, + DocumentSelector, + DynamicFeature, + InitializeParams, + LanguageClient, + LanguageClientOptions, + RegistrationData, + RPCMessageType, + ServerCapabilities, + ServerOptions +} from 'vscode-languageclient'; +import {XMLHttpRequest} from 'xmlhttprequest-ts'; -import { workspace, window, ExtensionContext } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from 'vscode-languageclient'; export function activate(context: ExtensionContext) { let script = 'java'; - let args = ['-jar',context.asAbsolutePath('gobpie-0.0.2-SNAPSHOT.jar')]; - + let args = ['-jar', context.asAbsolutePath('gobpie-0.0.2-SNAPSHOT.jar')]; + // Use this for communicating on stdio let serverOptions: ServerOptions = { - run : { command: script, args: args }, - debug: { command: script, args: args} , + run: {command: script, args: args}, + debug: {command: script, args: args}, }; - - /** - * Use this for debugging - * let serverOptions = () => { + + /** + * Use this for debugging + * let serverOptions = () => { const socket = net.connect({ port: 5007 }) const result: StreamInfo = { writer: socket, @@ -31,17 +41,70 @@ export function activate(context: ExtensionContext) { "-or- configure the extension to connect via standard IO.")) }) }*/ - + let clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'c' }], + documentSelector: [{scheme: 'file', language: 'c'}], synchronize: { configurationSection: 'c', - fileEvents: [ workspace.createFileSystemWatcher('**/*.c') ] + fileEvents: [workspace.createFileSystemWatcher('**/*.c')] } }; - + // Create the language client and start the client. - let lc : LanguageClient = new LanguageClient('GobPie','GobPie', serverOptions, clientOptions); + let lc: LanguageClient = new LanguageClient('GobPie', 'GobPie', serverOptions, clientOptions); + lc.registerFeature(new MagpieBridgeSupport(lc)); lc.start(); } + +export class MagpieBridgeSupport implements DynamicFeature { + constructor(private _client: LanguageClient) { + } + + messages: RPCMessageType | RPCMessageType[]; + fillInitializeParams?: (params: InitializeParams) => void; + + fillClientCapabilities(capabilities: ClientCapabilities): void { + capabilities.experimental = { + supportsShowHTML: true + } + } + + initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void { + this._client.onNotification("magpiebridge/showHTML", (content: string) => { + this.createWebView(content); + }); + } + + createWebView(content: string) { + let panel = window.createWebviewPanel("Customized Web View", "GobPie", ViewColumn.Beside, { + retainContextWhenHidden: true, + enableScripts: true + }); + panel.webview.html = content; + panel.webview.onDidReceiveMessage( + message => { + switch (message.command) { + case 'action': + var httpRequest = new XMLHttpRequest(); + var url = message.text; + httpRequest.open('GET', url); + httpRequest.send(); + return; + } + + } + ) + } + + register(message: RPCMessageType, data: RegistrationData): void { + } + + unregister(id: string): void { + } + + dispose(): void { + } + +} + From 6c1549a44b3ca66903fec16a77739fad254d282d Mon Sep 17 00:00:00 2001 From: karoliineh Date: Mon, 20 Jun 2022 12:47:21 +0300 Subject: [PATCH 02/11] Show CFG codelens for each function according the functions list from Goblint --- pom.xml | 4 +- src/main/java/analysis/GoblintAnalysis.java | 118 +++++++++++------- .../analysis/GoblintCFGAnalysisResult.java | 78 ++++++++++++ ...ava => GoblintMessagesAnalysisResult.java} | 38 ++---- src/main/java/analysis/ShowCFGCommand.java | 14 ++- .../java/goblintclient/GoblintClient.java | 41 +++--- .../communication/FunctionsResponse.java | 26 ++++ .../communication/MessagesResponse.java | 4 +- .../goblintclient/communication/Request.java | 3 + .../goblintclient/communication/Response.java | 2 + .../messages/GoblintFunctions.java | 53 ++++++++ .../messages/GoblintMessages.java | 36 +++--- vscode/installGobPie.sh | 2 +- vscode/package.json | 4 +- vscode/src/extension.ts | 2 +- 15 files changed, 308 insertions(+), 117 deletions(-) create mode 100644 src/main/java/analysis/GoblintCFGAnalysisResult.java rename src/main/java/analysis/{GoblintAnalysisResult.java => GoblintMessagesAnalysisResult.java} (65%) create mode 100644 src/main/java/goblintclient/communication/FunctionsResponse.java create mode 100644 src/main/java/goblintclient/messages/GoblintFunctions.java diff --git a/pom.xml b/pom.xml index 9ed561e..dc451d7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 goblint gobpie - 0.0.2-SNAPSHOT + 0.0.3-SNAPSHOT @@ -18,7 +18,7 @@ com.google.code.gson gson - 2.8.6 + 2.9.0 diff --git a/src/main/java/analysis/GoblintAnalysis.java b/src/main/java/analysis/GoblintAnalysis.java index 28a04da..343e2e7 100644 --- a/src/main/java/analysis/GoblintAnalysis.java +++ b/src/main/java/analysis/GoblintAnalysis.java @@ -1,35 +1,37 @@ package analysis; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.net.MalformedURLException; -import java.util.*; -import java.util.concurrent.TimeoutException; - import com.ibm.wala.classLoader.Module; - +import goblintclient.GoblintClient; +import goblintclient.communication.*; +import goblintserver.GoblintServer; +import gobpie.GobPieConfiguration; +import gobpie.GobPieException; +import gobpie.GobPieExceptionType; import magpiebridge.core.AnalysisConsumer; -import magpiebridge.core.ServerAnalysis; +import magpiebridge.core.AnalysisResult; import magpiebridge.core.MagpieServer; - +import magpiebridge.core.ServerAnalysis; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationObserver; 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.InvalidExitValueException; import org.zeroturnaround.exec.ProcessExecutor; import org.zeroturnaround.exec.ProcessResult; -import goblintclient.*; -import goblintclient.communication.*; -import goblintclient.messages.*; -import goblintserver.*; -import gobpie.*; +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; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class GoblintAnalysis implements ServerAnalysis { @@ -80,7 +82,7 @@ public void analyze(Collection files, AnalysisConsumer consume preAnalyse(); System.err.println("\n---------------------- Analysis started ----------------------"); - Collection response = reanalyse(); + Collection response = reanalyse(); if (response != null) server.consume(new ArrayList<>(response), source()); System.err.println("--------------------- Analysis finished ----------------------\n"); @@ -112,32 +114,53 @@ private void preAnalyse() { /** - * Sends the request to Goblint server to reanalyse and reads the result. + * Sends the requests to Goblint server and reads their results. * - * @return returns true if the request was sucessful, false 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 { - goblintClient.writeRequestToSocket(analyzeRequest); - AnalyzeResponse analyzeResponse = goblintClient.readAnalyzeResponseFromSocket(); - if (!analyzeRequest.getId().equals(analyzeResponse.getId())) - throw new GobPieException("Response ID does not match request ID.", GobPieExceptionType.GOBLINT_EXCEPTION); - 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); + // Analyze + getResponse(analyzeRequest); + // 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) { log.info("Sending the request to or receiving result from the server failed: " + e); return null; } } + /** + * 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. @@ -159,31 +182,38 @@ 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(msg -> { + try { + return msg.convert(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }).flatMap(List::stream).collect(Collectors.toList()); + } - Collection results = new ArrayList<>(); - try { - List messagesArray = messagesResponse.getResult(); - for (GoblintMessages msg : messagesArray) { - results.addAll(msg.convert()); + private Collection convertResultsFromJson(FunctionsResponse response) { + return response.getResult().stream().map(msg -> { + try { + return msg.convert(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); } - return results; - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + }).flatMap(List::stream).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..fa235ce --- /dev/null +++ b/src/main/java/analysis/GoblintCFGAnalysisResult.java @@ -0,0 +1,78 @@ +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() { + // TODO: "hardcoded" (currently needs manually executing the --html) + // TODO: Ask cfg from Goblint with a request instead + String cfgPath = "http://localhost:8080/cfgs/src%252F" + fileName + "/" + funName + ".svg"; + Command command = new Command("show cfg", "showcfg", Collections.singletonList(cfgPath)); + 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 65% rename from src/main/java/analysis/GoblintAnalysisResult.java rename to src/main/java/analysis/GoblintMessagesAnalysisResult.java index ea6ce44..bc477f0 100644 --- a/src/main/java/analysis/GoblintAnalysisResult.java +++ b/src/main/java/analysis/GoblintMessagesAnalysisResult.java @@ -2,33 +2,23 @@ import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.util.collections.Pair; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import magpiebridge.command.CodeActionCommand; +import goblintclient.messages.GoblintPosition; import magpiebridge.core.AnalysisResult; import magpiebridge.core.Kind; -// import magpiebridge.util.SourceCodeReader; - -import magpiebridge.util.SourceCodePositionUtils; -import org.eclipse.lsp4j.Command; -import org.eclipse.lsp4j.Diagnostic; 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; @@ -36,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; @@ -50,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; @@ -118,12 +108,4 @@ public String code() { return null; } - @Override - public Iterable command() { - // TODO: hardcoded - String cfgPath = "http://localhost:8080/cfgs/src%252Fexample.c/main.svg"; - Command command = new Command("show cfg", "showcfg", Collections.singletonList(cfgPath)); - return Collections.singleton(command); - } - } diff --git a/src/main/java/analysis/ShowCFGCommand.java b/src/main/java/analysis/ShowCFGCommand.java index 9d1982d..b1f0b35 100644 --- a/src/main/java/analysis/ShowCFGCommand.java +++ b/src/main/java/analysis/ShowCFGCommand.java @@ -4,17 +4,18 @@ import magpiebridge.core.MagpieClient; import magpiebridge.core.MagpieServer; import magpiebridge.core.WorkspaceCommand; -import magpiebridge.util.URIUtils; +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.awt.*; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; public class ShowCFGCommand implements WorkspaceCommand { + private final Logger log = LogManager.getLogger(ShowCFGCommand.class); + @Override public void execute(ExecuteCommandParams params, MagpieServer server, LanguageClient client) { try { @@ -25,6 +26,7 @@ public void execute(ExecuteCommandParams params, MagpieServer server, LanguageCl } else { uri = (String) uriJson; } + log.info("Showing CFG from: " + uri); showHTMLinClientOrBrowser(server, client, uri); } catch (IOException | URISyntaxException e) { MagpieServer.ExceptionLogger.log(e); @@ -63,11 +65,11 @@ public static void showHTMLinClientOrBrowser(MagpieServer server, LanguageClient ""; ((MagpieClient) client).showHTML(content); } - } else { - // TODO: probably does not work? + } /*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 be4fed5..be1e06f 100644 --- a/src/main/java/goblintclient/GoblintClient.java +++ b/src/main/java/goblintclient/GoblintClient.java @@ -1,32 +1,28 @@ 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.FunctionsResponse; import goblintclient.communication.MessagesResponse; +import goblintclient.communication.Request; 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. @@ -115,5 +111,18 @@ public MessagesResponse readMessagesResponseFromSocket() throws IOException { return messagesResponse; } + /** + * Method for reading the response from Goblint server. + * + * @return JsonObject of the results read from Goblint socket. + */ + + 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; + } + } diff --git a/src/main/java/goblintclient/communication/FunctionsResponse.java b/src/main/java/goblintclient/communication/FunctionsResponse.java new file mode 100644 index 0000000..0aa2840 --- /dev/null +++ b/src/main/java/goblintclient/communication/FunctionsResponse.java @@ -0,0 +1,26 @@ +package goblintclient.communication; + +import goblintclient.messages.GoblintFunctions; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Class FunctionsResponse. + *

+ * Corresponding object to the jsonrpc response JSON for functions request. + * + * @author Karoliine Holter + * @since 0.0.3 + */ + +public class FunctionsResponse extends Response { + // method: "functions" response: + // {"id":0,"jsonrpc":"2.0","result":[{"funName":"qsort","location":{"file":"/home/ ... }]} + + private final List result = new ArrayList<>(); + + public List getResult() { + return result; + } +} diff --git a/src/main/java/goblintclient/communication/MessagesResponse.java b/src/main/java/goblintclient/communication/MessagesResponse.java index 6a119f6..9b29684 100644 --- a/src/main/java/goblintclient/communication/MessagesResponse.java +++ b/src/main/java/goblintclient/communication/MessagesResponse.java @@ -1,10 +1,10 @@ package goblintclient.communication; +import goblintclient.messages.GoblintMessages; + import java.util.ArrayList; import java.util.List; -import goblintclient.messages.GoblintMessages; - /** * The Class MessagesResponse. *

diff --git a/src/main/java/goblintclient/communication/Request.java b/src/main/java/goblintclient/communication/Request.java index 68825a3..558a2e7 100644 --- a/src/main/java/goblintclient/communication/Request.java +++ b/src/main/java/goblintclient/communication/Request.java @@ -12,7 +12,10 @@ */ public class Request { + // Examples of requests used in this project: // {"jsonrpc":"2.0","id":0,"method":"analyze","params":{}} + // {"jsonrpc":"2.0","id":0,"method":"messages"} + // {"jsonrpc":"2.0","id":0,"method":"functions"} private final String jsonrpc = "2.0"; private final UUID id; diff --git a/src/main/java/goblintclient/communication/Response.java b/src/main/java/goblintclient/communication/Response.java index e71eb7b..9ef260e 100644 --- a/src/main/java/goblintclient/communication/Response.java +++ b/src/main/java/goblintclient/communication/Response.java @@ -16,6 +16,8 @@ public abstract class Response { // {"id":0,"jsonrpc":"2.0","result":{"status":["Success"]}} // method: "messages" response: // {"id":0,"jsonrpc":"2.0","result":[{"tags":[{"Category":["Race"]}], ... }]} + // method: "functions" response: + // {"id":0,"jsonrpc":"2.0","result":[{"funName":"qsort","location":{"file":"/home/ ... }]} private UUID id; private String jsonrpc = "2.0"; diff --git a/src/main/java/goblintclient/messages/GoblintFunctions.java b/src/main/java/goblintclient/messages/GoblintFunctions.java new file mode 100644 index 0000000..b2fe389 --- /dev/null +++ b/src/main/java/goblintclient/messages/GoblintFunctions.java @@ -0,0 +1,53 @@ +package goblintclient.messages; + +import analysis.GoblintCFGAnalysisResult; +import magpiebridge.core.AnalysisResult; + +import java.io.File; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; + +/** + * The Class GoblintFunctions. + *

+ * Corresponding object to the Goblint functions request response results in JSON. + * Converts the results from JSON to AnalysisResult requested by MagpieBridge. + * + * @author Karoliine Holter + * @since 0.0.3 + */ + +public class GoblintFunctions { + + String type = getClass().getName(); + + private String funName; + private location location; + + static class location { + private String file; + private int line; + private int column; + private int endLine; + private int endColumn; + } + + public String getType() { + return type; + } + + public List convert() throws MalformedURLException { + List results = new ArrayList<>(); + + results.add(new GoblintCFGAnalysisResult( + new GoblintPosition(location.line, location.endLine, location.column, location.endColumn, new File(location.file).toURI().toURL()), + funName, + new File(location.file).getName() + )); + + return results; + } + +} + diff --git a/src/main/java/goblintclient/messages/GoblintMessages.java b/src/main/java/goblintclient/messages/GoblintMessages.java index c633168..36e87db 100644 --- a/src/main/java/goblintclient/messages/GoblintMessages.java +++ b/src/main/java/goblintclient/messages/GoblintMessages.java @@ -3,7 +3,8 @@ import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.util.collections.Pair; -import analysis.GoblintAnalysisResult; +import analysis.GoblintMessagesAnalysisResult; +import magpiebridge.core.AnalysisResult; import java.io.File; import java.net.MalformedURLException; @@ -14,7 +15,7 @@ /** * The Class GoblintMessages. *

- * Corresponding object to the Goblint results in JSON. + * Corresponding object to the Goblint messages request response results in JSON. * Converts the results from JSON to AnalysisResult requested by MagpieBridge. * * @author Karoliine Holter @@ -23,6 +24,8 @@ public class GoblintMessages { + String type = getClass().getName(); + private final List tags = new ArrayList<>(); private String severity; private multipiece multipiece; @@ -84,30 +87,33 @@ static class loc { } } + public String getType() { + return type; + } - public List convert() throws MalformedURLException { - List results = new ArrayList<>(); + public List convert() throws MalformedURLException { + List results = new ArrayList<>(); if (multipiece.group_text == null) { - GoblintAnalysisResult result = createGoblintAnalysisResult(); + GoblintMessagesAnalysisResult result = createGoblintAnalysisResult(); results.add(result); } else { - List intermresults = new ArrayList<>(); + List intermresults = new ArrayList<>(); List pieces = multipiece.pieces; for (multipiece.pieces piece : pieces) { - GoblintAnalysisResult result = createGoblintAnalysisResult(piece); + GoblintMessagesAnalysisResult result = createGoblintAnalysisResult(piece); intermresults.add(result); } // Add related warnings to all the group elements - List addedRelated = new ArrayList<>(); - for (GoblintAnalysisResult res1 : intermresults) { + List addedRelated = new ArrayList<>(); + for (GoblintMessagesAnalysisResult res1 : intermresults) { List> related = new ArrayList<>(); - for (GoblintAnalysisResult res2 : intermresults) { + for (GoblintMessagesAnalysisResult res2 : intermresults) { if (res1 != res2) { related.add(Pair.make(res2.position(), res2.text())); } } - addedRelated.add(new GoblintAnalysisResult(res1.position(), res1.group_text() + "\n" + res1.text(), + addedRelated.add(new GoblintMessagesAnalysisResult(res1.position(), res1.group_text() + "\n" + res1.text(), res1.severityStr(), related)); } results.addAll(addedRelated); @@ -117,7 +123,7 @@ public List convert() throws MalformedURLException { } - public GoblintAnalysisResult createGoblintAnalysisResult() throws MalformedURLException { + public GoblintMessagesAnalysisResult createGoblintAnalysisResult() throws MalformedURLException { GoblintPosition pos = multipiece.loc == null ? new GoblintPosition(1, 1, 1, new File("").toURI().toURL()) : new GoblintPosition( @@ -127,11 +133,11 @@ public GoblintAnalysisResult createGoblintAnalysisResult() throws MalformedURLEx multipiece.loc.endColumn < 0 ? 10000 : multipiece.loc.endColumn - 1, new File(multipiece.loc.file).toURI().toURL()); String msg = tags.stream().map(tag::toString).collect(Collectors.joining("")) + " " + multipiece.text; - return new GoblintAnalysisResult(pos, msg, severity); + return new GoblintMessagesAnalysisResult(pos, msg, severity); } - public GoblintAnalysisResult createGoblintAnalysisResult(multipiece.pieces piece) throws MalformedURLException { + public GoblintMessagesAnalysisResult createGoblintAnalysisResult(multipiece.pieces piece) throws MalformedURLException { GoblintPosition pos = piece.loc == null ? new GoblintPosition(1, 1, 1, new File("").toURI().toURL()) : new GoblintPosition( @@ -140,7 +146,7 @@ public GoblintAnalysisResult createGoblintAnalysisResult(multipiece.pieces piece piece.loc.column < 0 ? 0 : piece.loc.column - 1, piece.loc.endColumn < 0 ? 10000 : piece.loc.endColumn - 1, new File(piece.loc.file).toURI().toURL()); - return new GoblintAnalysisResult(pos, + return new GoblintMessagesAnalysisResult(pos, tags.stream().map(tag::toString).collect(Collectors.joining("")) + " Group: " + multipiece.group_text, piece.text, severity); } diff --git a/vscode/installGobPie.sh b/vscode/installGobPie.sh index 4cc200d..395ea38 100755 --- a/vscode/installGobPie.sh +++ b/vscode/installGobPie.sh @@ -1,4 +1,4 @@ mvn clean -f "../../GobPie/pom.xml" mvn install -f "../../GobPie/pom.xml" echo y | vsce package -code --install-extension gobpie-0.0.2.vsix \ No newline at end of file +code --install-extension gobpie-0.0.3.vsix \ No newline at end of file diff --git a/vscode/package.json b/vscode/package.json index 438ddab..2dd365f 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -4,7 +4,7 @@ "description": "The interactive Goblint analyzer extension", "author": "Karoliine Holter", "license": "EPL-2.0", - "version": "0.0.2", + "version": "0.0.3", "repository": { "type": "git", "url": "https://github.com/karoliineh/MagpieBridge-Goblint.git" @@ -53,7 +53,7 @@ ] }, "scripts": { - "vscode:prepublish": "cp ../target/gobpie-0.0.2-SNAPSHOT.jar gobpie-0.0.2-SNAPSHOT.jar && npm run compile", + "vscode:prepublish": "cp ../target/gobpie-0.0.3-SNAPSHOT.jar gobpie-0.0.3-SNAPSHOT.jar && npm run compile", "compile": "tsc -b", "watch": "tsc -b -w" }, diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 7871250..cd13782 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -17,7 +17,7 @@ import {XMLHttpRequest} from 'xmlhttprequest-ts'; export function activate(context: ExtensionContext) { let script = 'java'; - let args = ['-jar', context.asAbsolutePath('gobpie-0.0.2-SNAPSHOT.jar')]; + let args = ['-jar', context.asAbsolutePath('gobpie-0.0.3-SNAPSHOT.jar')]; // Use this for communicating on stdio let serverOptions: ServerOptions = { From 8d96740b9c70e1a5954f8ea0906ddaf8691482f2 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Mon, 20 Jun 2022 20:16:43 +0300 Subject: [PATCH 03/11] Refactor convert functions --- src/main/java/analysis/GoblintAnalysis.java | 24 +++----- .../messages/GoblintFunctions.java | 22 ++++--- .../messages/GoblintMessages.java | 59 +++++++++++-------- 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/src/main/java/analysis/GoblintAnalysis.java b/src/main/java/analysis/GoblintAnalysis.java index 343e2e7..e69f90d 100644 --- a/src/main/java/analysis/GoblintAnalysis.java +++ b/src/main/java/analysis/GoblintAnalysis.java @@ -2,7 +2,12 @@ import com.ibm.wala.classLoader.Module; import goblintclient.GoblintClient; -import goblintclient.communication.*; +import goblintclient.communication.FunctionsResponse; +import goblintclient.communication.MessagesResponse; +import goblintclient.communication.Request; +import goblintclient.communication.Response; +import goblintclient.messages.GoblintFunctions; +import goblintclient.messages.GoblintMessages; import goblintserver.GoblintServer; import gobpie.GobPieConfiguration; import gobpie.GobPieException; @@ -24,7 +29,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; @@ -190,23 +194,11 @@ public ProcessResult runCommand(File dirPath, String[] command) throws IOExcepti */ private Collection convertResultsFromJson(MessagesResponse response) { - return response.getResult().stream().map(msg -> { - try { - return msg.convert(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - }).flatMap(List::stream).collect(Collectors.toList()); + return response.getResult().stream().map(GoblintMessages::convert).flatMap(List::stream).collect(Collectors.toList()); } private Collection convertResultsFromJson(FunctionsResponse response) { - return response.getResult().stream().map(msg -> { - try { - return msg.convert(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - }).flatMap(List::stream).collect(Collectors.toList()); + return response.getResult().stream().map(GoblintFunctions::convert).collect(Collectors.toList()); } diff --git a/src/main/java/goblintclient/messages/GoblintFunctions.java b/src/main/java/goblintclient/messages/GoblintFunctions.java index b2fe389..26a2bf4 100644 --- a/src/main/java/goblintclient/messages/GoblintFunctions.java +++ b/src/main/java/goblintclient/messages/GoblintFunctions.java @@ -5,8 +5,6 @@ import java.io.File; import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.List; /** * The Class GoblintFunctions. @@ -37,16 +35,16 @@ public String getType() { return type; } - public List convert() throws MalformedURLException { - List results = new ArrayList<>(); - - results.add(new GoblintCFGAnalysisResult( - new GoblintPosition(location.line, location.endLine, location.column, location.endColumn, new File(location.file).toURI().toURL()), - funName, - new File(location.file).getName() - )); - - return results; + public AnalysisResult convert() { + try { + return new GoblintCFGAnalysisResult( + new GoblintPosition(location.line, location.endLine, location.column, location.endColumn, new File(location.file).toURI().toURL()), + funName, + new File(location.file).getName() + ); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/goblintclient/messages/GoblintMessages.java b/src/main/java/goblintclient/messages/GoblintMessages.java index 36e87db..e6929ec 100644 --- a/src/main/java/goblintclient/messages/GoblintMessages.java +++ b/src/main/java/goblintclient/messages/GoblintMessages.java @@ -1,9 +1,8 @@ package goblintclient.messages; +import analysis.GoblintMessagesAnalysisResult; import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.util.collections.Pair; - -import analysis.GoblintMessagesAnalysisResult; import magpiebridge.core.AnalysisResult; import java.io.File; @@ -91,7 +90,7 @@ public String getType() { return type; } - public List convert() throws MalformedURLException { + public List convert() { List results = new ArrayList<>(); if (multipiece.group_text == null) { @@ -123,32 +122,40 @@ public List convert() throws MalformedURLException { } - public GoblintMessagesAnalysisResult createGoblintAnalysisResult() throws MalformedURLException { - GoblintPosition pos = multipiece.loc == null - ? new GoblintPosition(1, 1, 1, new File("").toURI().toURL()) - : new GoblintPosition( - multipiece.loc.line, - multipiece.loc.endLine, - multipiece.loc.column < 0 ? 0 : multipiece.loc.column - 1, - multipiece.loc.endColumn < 0 ? 10000 : multipiece.loc.endColumn - 1, - new File(multipiece.loc.file).toURI().toURL()); - String msg = tags.stream().map(tag::toString).collect(Collectors.joining("")) + " " + multipiece.text; - return new GoblintMessagesAnalysisResult(pos, msg, severity); + public GoblintMessagesAnalysisResult createGoblintAnalysisResult() { + try { + GoblintPosition pos = multipiece.loc == null + ? new GoblintPosition(1, 1, 1, new File("").toURI().toURL()) + : new GoblintPosition( + multipiece.loc.line, + multipiece.loc.endLine, + multipiece.loc.column < 0 ? 0 : multipiece.loc.column - 1, + multipiece.loc.endColumn < 0 ? 10000 : multipiece.loc.endColumn - 1, + new File(multipiece.loc.file).toURI().toURL()); + String msg = tags.stream().map(tag::toString).collect(Collectors.joining("")) + " " + multipiece.text; + return new GoblintMessagesAnalysisResult(pos, msg, severity); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } } - public GoblintMessagesAnalysisResult createGoblintAnalysisResult(multipiece.pieces piece) throws MalformedURLException { - GoblintPosition pos = piece.loc == null - ? new GoblintPosition(1, 1, 1, new File("").toURI().toURL()) - : new GoblintPosition( - piece.loc.line, - piece.loc.endLine, - piece.loc.column < 0 ? 0 : piece.loc.column - 1, - piece.loc.endColumn < 0 ? 10000 : piece.loc.endColumn - 1, - new File(piece.loc.file).toURI().toURL()); - return new GoblintMessagesAnalysisResult(pos, - tags.stream().map(tag::toString).collect(Collectors.joining("")) + " Group: " + multipiece.group_text, - piece.text, severity); + public GoblintMessagesAnalysisResult createGoblintAnalysisResult(multipiece.pieces piece) { + try { + GoblintPosition pos = piece.loc == null + ? new GoblintPosition(1, 1, 1, new File("").toURI().toURL()) + : new GoblintPosition( + piece.loc.line, + piece.loc.endLine, + piece.loc.column < 0 ? 0 : piece.loc.column - 1, + piece.loc.endColumn < 0 ? 10000 : piece.loc.endColumn - 1, + new File(piece.loc.file).toURI().toURL()); + return new GoblintMessagesAnalysisResult(pos, + tags.stream().map(tag::toString).collect(Collectors.joining("")) + " Group: " + multipiece.group_text, + piece.text, severity); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } } From f2592939b24c58ed8bd56f4323cd885d9f56b03a Mon Sep 17 00:00:00 2001 From: Karoliine Holter <44437975+karoliineh@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:36:00 +0300 Subject: [PATCH 04/11] Update build.yml Update node from 14 to 16 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 183d790..793fefa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,10 +18,10 @@ jobs: distribution: 'adopt' cache: maven - - name: Set up Node.js 14 + - name: Set up Node.js 16 uses: actions/setup-node@v2 with: - node-version: '14.x' # TODO: what version do we want? + node-version: '16.x' # TODO: what version do we want? # cache: npm # TODO: requires package-lock.json - name: Build with Maven From b3018f10fc14afa893a858b912a5ab0e388ace68 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Mon, 25 Jul 2022 15:44:16 +0300 Subject: [PATCH 05/11] Update dependencies and node to 18 --- .github/workflows/build.yml | 4 ++-- vscode/package.json | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 183d790..3b5132a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/vscode/package.json b/vscode/package.json index 2dd365f..e407a67 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -58,15 +58,15 @@ "watch": "tsc -b -w" }, "dependencies": { - "vscode-languageclient": "^5.2.1", + "vscode-languageclient": "^8.0.2", "xmlhttprequest-ts": "^1.0.1" }, "devDependencies": { "@vscode/test-electron": "^2.1.4", "@types/vscode": "^1.1.37", "@types/mocha": "^9.1.1", - "@types/node": "^11.13.0", - "tslint": "^5.15.0", - "typescript": "^3.4.2" + "@types/node": "^18.6.1", + "tslint": "^6.1.3", + "typescript": "^4.7.4" } } From 12ccaf80280224d96f259b41565cfad248a9cb87 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Mon, 25 Jul 2022 16:11:03 +0300 Subject: [PATCH 06/11] Revert vscode-languageclient version update --- vscode/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode/package.json b/vscode/package.json index e407a67..ac1200c 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -58,7 +58,7 @@ "watch": "tsc -b -w" }, "dependencies": { - "vscode-languageclient": "^8.0.2", + "vscode-languageclient": "^5.2.1", "xmlhttprequest-ts": "^1.0.1" }, "devDependencies": { From 48a2f2cf757745b0455ead5d89271c07503af9c2 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Tue, 26 Jul 2022 13:37:14 +0300 Subject: [PATCH 07/11] Request cfg from server using codelens --- src/main/java/Main.java | 16 ++++----- .../analysis/GoblintCFGAnalysisResult.java | 5 +-- src/main/java/analysis/ShowCFGCommand.java | 35 +++++++++++++++---- .../java/goblintclient/GoblintClient.java | 26 ++++++-------- .../communication/CFGsResponse.java | 25 +++++++++++++ .../goblintclient/communication/Request.java | 10 ++++++ .../goblintclient/messages/GoblintCFG.java | 18 ++++++++++ 7 files changed, 100 insertions(+), 35 deletions(-) create mode 100644 src/main/java/goblintclient/communication/CFGsResponse.java create mode 100644 src/main/java/goblintclient/messages/GoblintCFG.java diff --git a/src/main/java/Main.java b/src/main/java/Main.java index eb13fe5..781e3c9 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -37,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(); @@ -62,19 +65,10 @@ 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); - - magpieServer.addHttpServer(cfgHttpServer); - magpieServer.addCommand("showcfg", new ShowCFGCommand()); - - // launch magpieServer - magpieServer.launchOnStdio(); - log.info("MagpieBridge server launched."); - } @@ -110,6 +104,10 @@ private static void addAnalysis() { ServerAnalysis serverAnalysis = new GoblintAnalysis(magpieServer, goblintServer, goblintClient, gobpieConfiguration); Either analysis = Either.forLeft(serverAnalysis); magpieServer.addAnalysis(analysis, language); + + // TODO: move into separate function? + magpieServer.addHttpServer(cfgHttpServer); + magpieServer.addCommand("showcfg", new ShowCFGCommand(goblintClient)); } diff --git a/src/main/java/analysis/GoblintCFGAnalysisResult.java b/src/main/java/analysis/GoblintCFGAnalysisResult.java index fa235ce..fb05d5d 100644 --- a/src/main/java/analysis/GoblintCFGAnalysisResult.java +++ b/src/main/java/analysis/GoblintCFGAnalysisResult.java @@ -34,10 +34,7 @@ public GoblintCFGAnalysisResult(CAstSourcePositionMap.Position pos, String funNa @Override public Iterable command() { - // TODO: "hardcoded" (currently needs manually executing the --html) - // TODO: Ask cfg from Goblint with a request instead - String cfgPath = "http://localhost:8080/cfgs/src%252F" + fileName + "/" + funName + ".svg"; - Command command = new Command("show cfg", "showcfg", Collections.singletonList(cfgPath)); + Command command = new Command("show cfg", "showcfg", Collections.singletonList(funName)); return Collections.singleton(command); } diff --git a/src/main/java/analysis/ShowCFGCommand.java b/src/main/java/analysis/ShowCFGCommand.java index b1f0b35..4ed799a 100644 --- a/src/main/java/analysis/ShowCFGCommand.java +++ b/src/main/java/analysis/ShowCFGCommand.java @@ -1,6 +1,12 @@ package analysis; import com.google.gson.JsonPrimitive; +import goblintclient.GoblintClient; +import goblintclient.communication.CFGsResponse; +import goblintclient.communication.Request; +import goblintclient.messages.GoblintCFG; +import gobpie.GobPieException; +import gobpie.GobPieExceptionType; import magpiebridge.core.MagpieClient; import magpiebridge.core.MagpieServer; import magpiebridge.core.WorkspaceCommand; @@ -14,26 +20,43 @@ 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 uri; + String funName; Object uriJson = params.getArguments().get(0); if (uriJson instanceof JsonPrimitive) { - uri = ((JsonPrimitive) uriJson).getAsString(); + funName = ((JsonPrimitive) uriJson).getAsString(); } else { - uri = (String) uriJson; + funName = (String) uriJson; } - log.info("Showing CFG from: " + uri); - showHTMLinClientOrBrowser(server, client, uri); + 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(); } } + public String getCFG(String funName) { + Request cfgRequest = new Request("cfgs", funName); + try { + goblintClient.writeRequestToSocket(cfgRequest); + CFGsResponse cfGsResponse = goblintClient.readCFGsResponseFromSocket(); + return cfGsResponse.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. @@ -60,7 +83,7 @@ public static void showHTMLinClientOrBrowser(MagpieServer server, LanguageClient " \n" + " \n" + " \n" + - " \n" + + "

" + cfg + "

\n" + " \n" + ""; ((MagpieClient) client).showHTML(content); diff --git a/src/main/java/goblintclient/GoblintClient.java b/src/main/java/goblintclient/GoblintClient.java index d7d412c..7cebeb1 100644 --- a/src/main/java/goblintclient/GoblintClient.java +++ b/src/main/java/goblintclient/GoblintClient.java @@ -2,10 +2,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import goblintclient.communication.AnalyzeResponse; -import goblintclient.communication.FunctionsResponse; -import goblintclient.communication.MessagesResponse; -import goblintclient.communication.Request; +import goblintclient.communication.*; import goblintclient.messages.GoblintMessages; import goblintclient.messages.GoblintTagInterfaceAdapter; import gobpie.GobPieException; @@ -81,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. */ @@ -94,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(); @@ -111,11 +102,6 @@ public MessagesResponse readMessagesResponseFromSocket() throws IOException { return messagesResponse; } - /** - * Method for reading the response from Goblint server. - * - * @return JsonObject of the results read from Goblint socket. - */ public FunctionsResponse readFunctionsResponseFromSocket() throws IOException { String response = inputReader.readLine(); @@ -125,4 +111,12 @@ public FunctionsResponse readFunctionsResponseFromSocket() throws IOException { } + public CFGsResponse readCFGsResponseFromSocket() throws IOException { + String response = inputReader.readLine(); + CFGsResponse cfgsResponse = new Gson().fromJson(response, CFGsResponse.class); + log.info("Response " + cfgsResponse.getId() + " read from socket."); + return cfgsResponse; + } + + } diff --git a/src/main/java/goblintclient/communication/CFGsResponse.java b/src/main/java/goblintclient/communication/CFGsResponse.java new file mode 100644 index 0000000..fe55c87 --- /dev/null +++ b/src/main/java/goblintclient/communication/CFGsResponse.java @@ -0,0 +1,25 @@ +package goblintclient.communication; + +import goblintclient.messages.GoblintCFG; + +/** + * The Class CFGsResponse. + *

+ * Corresponding object to the jsonrpc response JSON for cfgs request. + * + * @author Karoliine Holter + * @since 0.0.3 + */ + +public class CFGsResponse extends Response { + + // method: "cfgs" response: + // {"id":0,"jsonrpc":"2.0","result":{"cfg":"digraph cfg {\n\tnode [id=\"\\N\", ... }} + + private GoblintCFG result; + + public GoblintCFG getResult() { + return result; + } +} + diff --git a/src/main/java/goblintclient/communication/Request.java b/src/main/java/goblintclient/communication/Request.java index 558a2e7..d578291 100644 --- a/src/main/java/goblintclient/communication/Request.java +++ b/src/main/java/goblintclient/communication/Request.java @@ -16,6 +16,7 @@ public class Request { // {"jsonrpc":"2.0","id":0,"method":"analyze","params":{}} // {"jsonrpc":"2.0","id":0,"method":"messages"} // {"jsonrpc":"2.0","id":0,"method":"functions"} + // {"jsonrpc":"2.0","id":0,"method":"cfgs", "params":{"fname":"main"}} private final String jsonrpc = "2.0"; private final UUID id; @@ -23,6 +24,7 @@ public class Request { private params params; static class params { + private String fname; } public Request(String method) { @@ -33,6 +35,14 @@ public Request(String method) { this.id = UUID.randomUUID(); } + public Request(String method, String fname) { + this.method = method; + this.params = new params(); + this.params.fname = fname; + this.id = UUID.randomUUID(); + } + + public String getJsonrpc() { return jsonrpc; } diff --git a/src/main/java/goblintclient/messages/GoblintCFG.java b/src/main/java/goblintclient/messages/GoblintCFG.java new file mode 100644 index 0000000..6533e2a --- /dev/null +++ b/src/main/java/goblintclient/messages/GoblintCFG.java @@ -0,0 +1,18 @@ +package goblintclient.messages; + +/** + * The Class GoblintCFG. + *

+ * Corresponding object to the Goblint cfgs request response results in JSON. + * + * @author Karoliine Holter + * @since 0.0.3 + */ + +public class GoblintCFG { + private String cfg; + + public String getCfg() { + return cfg; + } +} From 9d94269ba880029b115bc6d9fba5209481daff06 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Tue, 26 Jul 2022 14:53:32 +0300 Subject: [PATCH 08/11] Use graphviz-java to generate svg from dot --- pom.xml | 7 +++++ src/main/java/analysis/ShowCFGCommand.java | 31 +++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 9ebdca4..2d14591 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,13 @@ 0.1.5 + + + guru.nidi + graphviz-java + 0.18.1 + + com.google.code.gson diff --git a/src/main/java/analysis/ShowCFGCommand.java b/src/main/java/analysis/ShowCFGCommand.java index 4ed799a..ec13740 100644 --- a/src/main/java/analysis/ShowCFGCommand.java +++ b/src/main/java/analysis/ShowCFGCommand.java @@ -4,9 +4,10 @@ import goblintclient.GoblintClient; import goblintclient.communication.CFGsResponse; import goblintclient.communication.Request; -import goblintclient.messages.GoblintCFG; 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; @@ -15,6 +16,7 @@ import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.services.LanguageClient; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URISyntaxException; @@ -46,12 +48,22 @@ public void execute(ExecuteCommandParams params, MagpieServer server, LanguageCl } } + /** + * 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("cfgs", funName); + Request cfgsRequest = new Request("cfgs", funName); try { - goblintClient.writeRequestToSocket(cfgRequest); - CFGsResponse cfGsResponse = goblintClient.readCFGsResponseFromSocket(); - return cfGsResponse.getResult().getCfg(); + goblintClient.writeRequestToSocket(cfgsRequest); + CFGsResponse cfgsResponse = goblintClient.readCFGsResponseFromSocket(); + if (!cfgsRequest.getId().equals(cfgsResponse.getId())) + throw new GobPieException("Response ID does not match request ID.", GobPieExceptionType.GOBLINT_EXCEPTION); + return cfgsResponse.getResult().getCfg(); } catch (IOException e) { throw new GobPieException("Sending the request to or receiving result from the server failed.", e, GobPieExceptionType.GOBLINT_EXCEPTION); } @@ -67,9 +79,15 @@ public String getCFG(String funName) { * @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" + @@ -83,7 +101,8 @@ public static void showHTMLinClientOrBrowser(MagpieServer server, LanguageClient " \n" + " \n" + " \n" + - "

" + cfg + "

\n" + + /*" " +*/ + " " + svg + "" + " \n" + ""; ((MagpieClient) client).showHTML(content); From 4843cda05133c1bcb830f196a51cb4fd3ba83a56 Mon Sep 17 00:00:00 2001 From: karoliineh Date: Tue, 26 Jul 2022 16:44:48 +0300 Subject: [PATCH 09/11] Consistency: rename cfgs to cfg (to match the request name in Goblint) --- src/main/java/analysis/ShowCFGCommand.java | 12 ++++++------ src/main/java/goblintclient/GoblintClient.java | 8 ++++---- .../{CFGsResponse.java => CFGResponse.java} | 9 ++++----- .../java/goblintclient/communication/Request.java | 2 +- .../java/goblintclient/communication/Response.java | 2 ++ src/main/java/goblintclient/messages/GoblintCFG.java | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) rename src/main/java/goblintclient/communication/{CFGsResponse.java => CFGResponse.java} (62%) diff --git a/src/main/java/analysis/ShowCFGCommand.java b/src/main/java/analysis/ShowCFGCommand.java index ec13740..ba8ccf0 100644 --- a/src/main/java/analysis/ShowCFGCommand.java +++ b/src/main/java/analysis/ShowCFGCommand.java @@ -2,7 +2,7 @@ import com.google.gson.JsonPrimitive; import goblintclient.GoblintClient; -import goblintclient.communication.CFGsResponse; +import goblintclient.communication.CFGResponse; import goblintclient.communication.Request; import gobpie.GobPieException; import gobpie.GobPieExceptionType; @@ -57,13 +57,13 @@ public void execute(ExecuteCommandParams params, MagpieServer server, LanguageCl */ public String getCFG(String funName) { - Request cfgsRequest = new Request("cfgs", funName); + Request cfgRequest = new Request("cfg", funName); try { - goblintClient.writeRequestToSocket(cfgsRequest); - CFGsResponse cfgsResponse = goblintClient.readCFGsResponseFromSocket(); - if (!cfgsRequest.getId().equals(cfgsResponse.getId())) + 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 cfgsResponse.getResult().getCfg(); + 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); } diff --git a/src/main/java/goblintclient/GoblintClient.java b/src/main/java/goblintclient/GoblintClient.java index 7cebeb1..0c1e0ba 100644 --- a/src/main/java/goblintclient/GoblintClient.java +++ b/src/main/java/goblintclient/GoblintClient.java @@ -111,11 +111,11 @@ public FunctionsResponse readFunctionsResponseFromSocket() throws IOException { } - public CFGsResponse readCFGsResponseFromSocket() throws IOException { + public CFGResponse readCFGResponseFromSocket() throws IOException { String response = inputReader.readLine(); - CFGsResponse cfgsResponse = new Gson().fromJson(response, CFGsResponse.class); - log.info("Response " + cfgsResponse.getId() + " read from socket."); - return cfgsResponse; + 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/CFGsResponse.java b/src/main/java/goblintclient/communication/CFGResponse.java similarity index 62% rename from src/main/java/goblintclient/communication/CFGsResponse.java rename to src/main/java/goblintclient/communication/CFGResponse.java index fe55c87..fc6edb1 100644 --- a/src/main/java/goblintclient/communication/CFGsResponse.java +++ b/src/main/java/goblintclient/communication/CFGResponse.java @@ -3,17 +3,16 @@ import goblintclient.messages.GoblintCFG; /** - * The Class CFGsResponse. + * The Class CFGResponse. *

- * Corresponding object to the jsonrpc response JSON for cfgs request. + * Corresponding object to the jsonrpc response JSON for cfg request. * * @author Karoliine Holter * @since 0.0.3 */ -public class CFGsResponse extends Response { - - // method: "cfgs" response: +public class CFGResponse extends Response { + // method: "cfg" response: // {"id":0,"jsonrpc":"2.0","result":{"cfg":"digraph cfg {\n\tnode [id=\"\\N\", ... }} private GoblintCFG result; diff --git a/src/main/java/goblintclient/communication/Request.java b/src/main/java/goblintclient/communication/Request.java index d578291..77639d2 100644 --- a/src/main/java/goblintclient/communication/Request.java +++ b/src/main/java/goblintclient/communication/Request.java @@ -16,7 +16,7 @@ public class Request { // {"jsonrpc":"2.0","id":0,"method":"analyze","params":{}} // {"jsonrpc":"2.0","id":0,"method":"messages"} // {"jsonrpc":"2.0","id":0,"method":"functions"} - // {"jsonrpc":"2.0","id":0,"method":"cfgs", "params":{"fname":"main"}} + // {"jsonrpc":"2.0","id":0,"method":"cfg", "params":{"fname":"main"}} private final String jsonrpc = "2.0"; private final UUID id; diff --git a/src/main/java/goblintclient/communication/Response.java b/src/main/java/goblintclient/communication/Response.java index 9ef260e..9cea179 100644 --- a/src/main/java/goblintclient/communication/Response.java +++ b/src/main/java/goblintclient/communication/Response.java @@ -18,6 +18,8 @@ public abstract class Response { // {"id":0,"jsonrpc":"2.0","result":[{"tags":[{"Category":["Race"]}], ... }]} // method: "functions" response: // {"id":0,"jsonrpc":"2.0","result":[{"funName":"qsort","location":{"file":"/home/ ... }]} + // method: "cfg" response: + // {"id":0,"jsonrpc":"2.0","result":{"cfg":"digraph cfg {\n\tnode [id=\"\\N\", ... }} private UUID id; private String jsonrpc = "2.0"; diff --git a/src/main/java/goblintclient/messages/GoblintCFG.java b/src/main/java/goblintclient/messages/GoblintCFG.java index 6533e2a..a80ecb5 100644 --- a/src/main/java/goblintclient/messages/GoblintCFG.java +++ b/src/main/java/goblintclient/messages/GoblintCFG.java @@ -3,7 +3,7 @@ /** * The Class GoblintCFG. *

- * Corresponding object to the Goblint cfgs request response results in JSON. + * Corresponding object to the Goblint cfg request response results in JSON. * * @author Karoliine Holter * @since 0.0.3 From 769d33d81b211b1097d439fe72a403acd696041c Mon Sep 17 00:00:00 2001 From: karoliineh Date: Tue, 26 Jul 2022 16:52:29 +0300 Subject: [PATCH 10/11] Improve comments --- src/main/java/Main.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 781e3c9..ebcd147 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -73,8 +73,7 @@ private static void createMagpieServer() { /** - * Method for creating and adding GoblintAnalysis to MagpieBridge server - * and doing the initial analysis. + * Method for creating and adding Goblint analysis to MagpieBridge server. *

* Creates GoblintServer, GoblintClient and the GoblintAnalysis classes. * @@ -105,7 +104,7 @@ private static void addAnalysis() { Either analysis = Either.forLeft(serverAnalysis); magpieServer.addAnalysis(analysis, language); - // TODO: move into separate function? + // add HTTP server for showing CFGs magpieServer.addHttpServer(cfgHttpServer); magpieServer.addCommand("showcfg", new ShowCFGCommand(goblintClient)); } From 4c8443990558a2c3407a8fe5a91ebf469995e9ec Mon Sep 17 00:00:00 2001 From: karoliineh Date: Tue, 26 Jul 2022 18:08:29 +0300 Subject: [PATCH 11/11] Show cfgs in one tab instead of opening a new tab every time --- vscode/src/extension.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 88a866e..046f632 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -1,4 +1,5 @@ 'use strict'; +import * as vscode from 'vscode'; import {ExtensionContext, ViewColumn, window, workspace} from 'vscode'; import { ClientCapabilities, @@ -14,6 +15,8 @@ import { } from 'vscode-languageclient'; import {XMLHttpRequest} from 'xmlhttprequest-ts'; +// Track currently webview panel +let panel: vscode.WebviewPanel | undefined = undefined; export function activate(context: ExtensionContext) { let script = 'java'; @@ -77,10 +80,27 @@ export class MagpieBridgeSupport implements DynamicFeature { } createWebView(content: string) { - let panel = window.createWebviewPanel("Customized Web View", "GobPie", ViewColumn.Beside, { - retainContextWhenHidden: true, - enableScripts: true - }); + const columnToShowIn = ViewColumn.Beside; + + if (panel) { + // If we already have a panel, show it in the target column + panel.reveal(columnToShowIn); + } else { + // Otherwise, create a new panel + panel = window.createWebviewPanel("Customized Web View", "GobPie", columnToShowIn, { + retainContextWhenHidden: true, + enableScripts: true + }); + + // Reset when the current panel is closed + panel.onDidDispose( + () => { + panel = undefined; + }, + null, + ); + } + panel.webview.html = content; panel.webview.onDidReceiveMessage( message => {