From 1f890ada79d678399780c541201581b723bd3f59 Mon Sep 17 00:00:00 2001 From: Valentin Date: Sat, 19 Sep 2020 01:02:07 +0200 Subject: [PATCH 01/79] Hooked Viper IDE to ViperServer --- build.sbt | 1 + .../viper/server/IdeLanguageClient.scala | 32 +++++++ src/main/scala/viper/server/ViperConfig.scala | 2 +- .../viper/server/ViperLanguageServer.scala | 93 +++++++++++++++++++ src/main/scala/viper/server/ViperServer.scala | 49 ++++++++-- 5 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 src/main/scala/viper/server/IdeLanguageClient.scala create mode 100644 src/main/scala/viper/server/ViperLanguageServer.scala diff --git a/build.sbt b/build.sbt index a8979e5..a4965b9 100644 --- a/build.sbt +++ b/build.sbt @@ -42,6 +42,7 @@ lazy val server = (project in file(".")) libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.22", libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % "2.5.22" % Test, libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % "10.1.8" % Test, + libraryDependencies += "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.8.1", // Java implementation of language server protocol // Run settings run / javaOptions += "-Xss128m", diff --git a/src/main/scala/viper/server/IdeLanguageClient.scala b/src/main/scala/viper/server/IdeLanguageClient.scala new file mode 100644 index 0000000..8d8fc38 --- /dev/null +++ b/src/main/scala/viper/server/IdeLanguageClient.scala @@ -0,0 +1,32 @@ +package viper.server + +import org.eclipse.lsp4j.services.LanguageClient +import org.eclipse.lsp4j.jsonrpc.services.JsonNotification + + +trait IdeLanguageClient extends LanguageClient { +// @JsonNotification("gobraServer/noVerificationInformation") +// def noVerificationInformation(): Unit +// +// @JsonNotification("gobraServer/overallResult") +// def overallResult(params: String): Unit +// +// @JsonNotification("gobraServer/verificationProgress") +// def verificationProgress(fileUri: String, progress: Int): Unit +// +// @JsonNotification("gobraServer/verificationException") +// def verificationException(fileUri: String): Unit +// +// +// @JsonNotification("gobraServer/finishedGoifying") +// def finishedGoifying(fileUri: String, success: Boolean): Unit +// +// @JsonNotification("gobraServer/finishedGobrafying") +// def finishedGobrafying(oldFilePath: String, newFilePath: String, success: Boolean): Unit +// +// @JsonNotification("gobraServer/finishedViperCodePreview") +// def finishedViperCodePreview(ast: String, highlighted: String): Unit +// +// @JsonNotification("gobraServer/finishedInternalCodePreview") +// def finishedInternalCodePreview(internal: String, highlighted: String): Unit +} \ No newline at end of file diff --git a/src/main/scala/viper/server/ViperConfig.scala b/src/main/scala/viper/server/ViperConfig.scala index 07d1160..e9b86a4 100644 --- a/src/main/scala/viper/server/ViperConfig.scala +++ b/src/main/scala/viper/server/ViperConfig.scala @@ -87,7 +87,7 @@ class ViperConfig(args: Seq[String]) extends ScallopConf(args) { + "If the option is omitted, a default timeout of 5000 milliseconds will be set."), default = Some(5000), noshort = false, - hidden = false + hidden = true ) val maximumActiveJobs: ScallopOption[Int] = opt[Int]("maximumActiveJobs", 'm', diff --git a/src/main/scala/viper/server/ViperLanguageServer.scala b/src/main/scala/viper/server/ViperLanguageServer.scala new file mode 100644 index 0000000..cd412cf --- /dev/null +++ b/src/main/scala/viper/server/ViperLanguageServer.scala @@ -0,0 +1,93 @@ +package viper.server + +import java.util.concurrent.CompletableFuture +import scala.collection.JavaConverters._ + +import com.google.gson.JsonPrimitive +import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} +import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionOptions, CompletionParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, MessageParams, MessageType, ServerCapabilities, TextDocumentSyncKind} +import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware, LanguageServer, TextDocumentService, WorkspaceService} + +class ViperLanguageServer extends LanguageClientAware { + private var _client: Option[LanguageClient] = None + + //==========================================================================// + // ADD MORE METHODS TO UNDERSTAND CLIENT HERE // + //==========================================================================// + + @JsonNotification("sbi/hi") + def saidHi(params: DidSaveTextDocumentParams): Unit = { + println("said hi") + _client match { + case Some(c) => c.showMessage(new MessageParams(MessageType.Info, "Hi back <3")) + case _ => + } + } + + @JsonNotification("textDocument/didChange") + def didChange(params: DidChangeTextDocumentParams): Unit = { + println("didChange") + _client match { + case Some(c) => + c.showMessage(new MessageParams(MessageType.Log, s"Huuuhuuuuu")) + case _ => + } + } + + @JsonRequest(value = "initialize") + def initialize(params: InitializeParams): CompletableFuture[InitializeResult] = { + println("initialize") + val capabilities = new ServerCapabilities() + // always send full text document for each notification: + capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) + capabilities.setCompletionProvider(new CompletionOptions(true, null)) + CompletableFuture.completedFuture(new InitializeResult(capabilities)) + } + + @JsonRequest(value = "shutdown") + def shutdown(): CompletableFuture[AnyRef] = { + println("shutdown") + CompletableFuture.completedFuture(null) + } + + @JsonNotification(value = "exit") + def exit(): Unit = { + println("exit") + sys.exit() + } + + + @JsonRequest("textDocument/completion") + def completion(params: CompletionParams): CompletableFuture[CompletionList] = { + val tsItem = new CompletionItem("TypeScript") + tsItem.setKind(CompletionItemKind.Text) + tsItem.setData(1) + val jsItem = new CompletionItem("JavaScript") + jsItem.setKind(CompletionItemKind.Text) + jsItem.setData(2) + val completions = new CompletionList(List(tsItem, jsItem).asJava) + CompletableFuture.completedFuture(completions) + } + + @JsonRequest("completionItem/resolve") + def completionItemResolve(item: CompletionItem): CompletableFuture[CompletionItem] = { + val data: Object = item.getData + data match { + case n: JsonPrimitive if n.getAsInt == 1 => { + item.setDetail("TypeScript details") + item.setDocumentation("TypeScript documentation") + } + case n: JsonPrimitive if n.getAsInt == 2 => { + item.setDetail("JavaScript details") + item.setDocumentation("JavaScript documentation") + } + case _ => item.setDetail(s"${data.toString} is instance of ${data.getClass}") + } + CompletableFuture.completedFuture(item) + } + + override def connect(client: LanguageClient): Unit = { + println("Connecting plugin client.") + _client = Some(client) + } +} diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index e3991ae..ed201ed 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -1,17 +1,46 @@ package viper.server -object ViperServerRunner { +import java.io.IOException +import java.net.Socket - var httpServer: ViperHttpServer = _ +import org.eclipse.lsp4j.jsonrpc.Launcher +import org.eclipse.lsp4j.services.LanguageClient - /** Start VCS in HTTP mode. - * */ - def startHttpServer(args: Array[String]): Unit = { - httpServer = new ViperHttpServer(args) - httpServer.start() - } +object ViperServerRunner { def main(args: Array[String]): Unit = { - startHttpServer(args) - } // method main + try { + var port = Integer.parseInt(args.head) + runServer(port) + } catch { + case e: NoSuchElementException => { + println("No port number provided") + sys.exit(1) + return + } + case e: NumberFormatException => { + println("Invalid port number") + sys.exit(1) + return + } + } + } + + def runServer(port: Int): Unit = { + // start listening on port + try { + val socket = new Socket("localhost", port) + println(s"going to listen on port $port") + + val server: ViperLanguageServer = new ViperLanguageServer() + val launcher = Launcher.createLauncher(server, classOf[LanguageClient], socket.getInputStream, socket.getOutputStream) + server.connect(launcher.getRemoteProxy) + // start listening on input stream in a new thread: + val fut = launcher.startListening() + // wait until stream is closed again + fut.get() + } catch { + case e: IOException => println(s"IOException occurred: ${e.toString}") + } + } } From d6ca33d9693df2e3b220dc52a030b9c2864db46f Mon Sep 17 00:00:00 2001 From: Valentin Date: Sat, 3 Oct 2020 12:25:54 +0200 Subject: [PATCH 02/79] implementing language server --- .../scala/viper/server/CommandProtocol.scala | 40 + src/main/scala/viper/server/Common.scala | 323 +++++++ src/main/scala/viper/server/Coordinator.scala | 75 ++ .../scala/viper/server/DataProtocol.scala | 199 ++++ .../viper/server/IdeLanguageClient.scala | 41 +- src/main/scala/viper/server/IdeLog.scala | 56 ++ .../viper/server/LanguageServerReceiver.scala | 217 +++++ src/main/scala/viper/server/Progress.scala | 33 + src/main/scala/viper/server/Settings.scala | 891 ++++++++++++++++++ .../scala/viper/server/VerificationTask.scala | 328 +++++++ .../viper/server/ViperLanguageServer.scala | 93 -- src/main/scala/viper/server/ViperServer.scala | 17 +- .../viper/server/ViperServerService.scala | 273 ++++++ .../server/core/VerificationWorker.scala | 1 - .../viper/server/core/ViperCoreServer.scala | 2 +- .../server/protocol/ViperIDEProtocol.scala | 26 +- 16 files changed, 2499 insertions(+), 116 deletions(-) create mode 100644 src/main/scala/viper/server/CommandProtocol.scala create mode 100644 src/main/scala/viper/server/Common.scala create mode 100644 src/main/scala/viper/server/Coordinator.scala create mode 100644 src/main/scala/viper/server/DataProtocol.scala create mode 100644 src/main/scala/viper/server/IdeLog.scala create mode 100644 src/main/scala/viper/server/LanguageServerReceiver.scala create mode 100644 src/main/scala/viper/server/Progress.scala create mode 100644 src/main/scala/viper/server/Settings.scala create mode 100644 src/main/scala/viper/server/VerificationTask.scala delete mode 100644 src/main/scala/viper/server/ViperLanguageServer.scala create mode 100644 src/main/scala/viper/server/ViperServerService.scala diff --git a/src/main/scala/viper/server/CommandProtocol.scala b/src/main/scala/viper/server/CommandProtocol.scala new file mode 100644 index 0000000..e505e7e --- /dev/null +++ b/src/main/scala/viper/server/CommandProtocol.scala @@ -0,0 +1,40 @@ +package viper.server + +object C2S_Commands { + val RequestBackendNames = "RequestBackendNames" //void + val Dispose = "Dispose" //void + val Verify = "Verify" //VerifyParams + val StopVerification = "StopVerification"//filePath:String + val ShowHeap = "ShowHeap"//ShowHeapParams + val StartBackend = "StartBackend"//backendName:String + val StopBackend = "StopBackend"//void + val SwapBackend = "SwapBackend"//backendName:String + val GetExecutionTrace = "GetExecutionTrace"//GetExecutionTraceParams -> trace:ExecutionTrace[] + val RemoveDiagnostics = "RemoveDiagnostics" + val UpdateViperTools = "UpdateViperTools" + val GetViperFileEndings = "GetViperFileEndings" + val ViperUpdateComplete = "ViperUpdateComplete" + val FlushCache = "FlushCache" + val GetIdentifier = "GetIdentifier" +} + +object S2C_Commands { + val BackendChange = "BackendChange" + val CheckIfSettingsVersionsSpecified = "CheckIfSettingsVersionsSpecified" + val SettingsChecked = "SettingsChecked" //SettingsCheckedParams + val RequestRequiredVersion = "RequestRequiredVersion" //void -> requiredVersions: Versions + val StateChange = "StateChange" //StateChangeParams + val Log = "Log" //LogParams + val Error = "Error" //LogParams + val ToLogFile = "ToLogFile" //LogParams + val Hint = "Hint" //message: String + val Progress = "Progress" //message: {domain:String, curr:number, total:number} + val FileOpened = "FileOpened" //uri: String + val FileClosed = "FileClosed" //uri: String + val VerificationNotStarted = "VerificationNotStarted" //uri: String + val StopDebugging = "StopDebugging" //void + val BackendReady = "BackendReady" //BackendReadyParams + val StepsAsDecorationOptions = "StepsAsDecorationOptions" //StepsAsDecorationOptionsResult + val HeapGraph = "HeapGraph" //HeapGraph + val UnhandledViperServerMessageType = "UnhandledViperServerMessageType" +} \ No newline at end of file diff --git a/src/main/scala/viper/server/Common.scala b/src/main/scala/viper/server/Common.scala new file mode 100644 index 0000000..a693661 --- /dev/null +++ b/src/main/scala/viper/server/Common.scala @@ -0,0 +1,323 @@ +package viper.server + +import java.nio.file.Paths +import java.util.concurrent.CompletableFuture + +import org.eclipse.lsp4j.{Position} + +object Common { + var viperFileEndings: Array[String] = _ + + def uriToPath(uri: String): String = { + Paths.get(uri).toString + } + + def pathToUri(path: String): String = { + Paths.get(path).toUri.toString + } + + def filenameFromPath(path: String): String = { + //Todo + "TODO" + } + + def refreshEndings(): CompletableFuture[Unit] = { + Coordinator.client.requestVprFileEndings().thenApply(endings => { + viperFileEndings = endings + }).exceptionally(e => { + Log.debug(s"GetViperFileEndings request was rejected by the client: $e") + }) + } + + def isViperSourceFile(uri: String): CompletableFuture[Boolean] = { + def areEndingsDefined: Boolean = viperFileEndings != null && viperFileEndings.nonEmpty + if (areEndingsDefined) { + val endingMatches = viperFileEndings.exists(ending => uri.endsWith(ending)) + CompletableFuture.completedFuture(endingMatches) + } else { // need to refresh endings and then compare + Log.debug("Refreshing the viper file endings.") + refreshEndings().thenApply(_ => { + if (areEndingsDefined) { + viperFileEndings.exists(ending => uri.endsWith(ending)) + } else { + false + } + }) + } + } + + def prettyRange(range: Range): String = { + s"${prettyPos(range.start)}-${prettyPos(range.end)}" + } + + def prettyPos(pos: Position): String = { + s"${pos.line + 1}:${pos.character + 1}" + } + + def getPositionOfState(index): Position = { + if (index >= 0 && index < this.steps.length) { + if (this.steps[index].position) { + return this.steps[index].position + } else { + return { + line: 0 + , character: 0 + } + } + } else { + return { + line: -1 + , character: -1 + } + } + } + + def comparePosition(a: Position, b: Position): Int = { + if (a == null && b == null) return 0 + if (a != null) return -1 + if (b != null) return 1 + if (a.getLine < b.getLine || (a.getLine == b.getLine && a.getCharacter < b.getCharacter)) { + -1 + } else if (a.getLine == b.getLine && a.getCharacter == b.getCharacter) { + 0 + } else { + 1 + } + } + + private def comparePositionAndIndex(a: Statement, b: Statement): Int = { + if (!a && !b) return 0 + if (!a) return -1 + if (!b) return 1 + if (a.position.line < b.position.line || (a.position.line === b.position.line && a.position.character < b.position.character)) { + return -1 + } else if (a.position.line === b.position.line && a.position.character === b.position.character) { + return (a.index < b.index) ? -1: 1 + } else { + return 1 + } + } + + private def compareByIndex(a: Statement, b: Statement): Int = { + if (!a && !b) return 0 + if (!a) return -1 + if (!b) return 1 + if (a.index < b.index) { + return -1 + } else if (a.index === b.index) { + return 0 + } else { + return 1 + } + } + + private def prettySteps(steps: Array[Statement]): String = { + try { + val res: String = "" + val methodIndex = - 1 + val currentMethodOffset = - 1 + val maxLine = 0 + val indent = "" + val allBordersPrinted = false + + // val currentMethod + + val numberOfClientSteps = 0 + steps.foreach((element, i) => { + val clientNumber = element.decorationOptions ? "" + element.decorationOptions.numberToDisplay: "" + + if (element.canBeShownAsDecoration) { + numberOfClientSteps ++ + } + + val parent = element.getClientParent () + if (parent && element.decorationOptions) { + clientNumber += " " + parent.decorationOptions.numberToDisplay + } + + val serverNumber = "" + i + val spacesToPut = 8 - clientNumber.length - serverNumber.length + spacesToPut = spacesToPut < 0 ? 0: spacesToPut + res += `\n\t${clientNumber} ${"\t".repeat(spacesToPut)}(${serverNumber})|${"\t".repeat(element.depthLevel())} ${element.firstLine()}` + }) + + res += '\ nNumberOfClientSteps: ' + numberOfClientSteps + //Log.log("Steps:\n" + res, LogLevel.LowLevelDebug) + return res + } catch (e) { + Log.debug ("Runtime Error in Pretty Steps: " + e) + } + } + + //Helper methods for child processes +// def executer(command: String, dataHandler?: (String) => void, errorHandler?: (String) => void, exitHandler?: () => void): child_process.ChildProcess = { +// try { +// Log.logWithOrigin("executer", command, LogLevel.Debug) +// val child: child_process.ChildProcess = child_process.exec(command, function (error, stdout, stderr) { +// Log.logWithOrigin('executer:stdout', stdout, LogLevel.LowLevelDebug) +// if (dataHandler) { +// dataHandler(stdout) +// } +// Log.logWithOrigin('executer:stderr', stderr, LogLevel.LowLevelDebug) +// if (errorHandler) { +// errorHandler(stderr) +// } +// if (error !== null) { +// Log.logWithOrigin('executer', ''+error, LogLevel.LowLevelDebug) +// } +// if (exitHandler) { +// Log.logWithOrigin('executer', 'done', LogLevel.LowLevelDebug) +// exitHandler() +// } +// }) +// return child +// } catch (e) { +// Log.error("Error executing " + command + ": " + e) +// } +// } + +// def sudoExecuter(command: String, name: String, callback) = { +// Log.log("sudo-executer: " + command, LogLevel.Debug) +// val options = { name: name } +// val child = sudo.exec(command, options, function (error, stdout, stderr) { +// Log.logWithOrigin('stdout', stdout, LogLevel.LowLevelDebug) +// Log.logWithOrigin('stderr', stderr, LogLevel.LowLevelDebug) +// if (error) { +// Log.error('sudo-executer error: ' + error) +// } +// callback() +// }) +// } + +// def spawner(command: String, args: String[]): child_process.ChildProcess = { +// Log.log("spawner: " + command + " " + args.join(" "), LogLevel.Debug) +// try { +// val child = child_process.spawn(command, args, { detached: true }) +// child.on('stdout', data => { +// Log.logWithOrigin('spawner stdout', data, LogLevel.LowLevelDebug) +// }) +// child.on('stderr', data => { +// Log.logWithOrigin('spawner stderr', data, LogLevel.LowLevelDebug) +// }) +// child.on('exit', data => { +// Log.log('spawner done: ' + data, LogLevel.LowLevelDebug) +// }) +// return child +// } catch (e) { +// Log.error("Error spawning command: " + e) +// } +// } + +// def backendRestartNeeded(settings: ViperSettings, oldBackendName: String, newBackendName: String) = { +// if (!settings) +// return true +// +// val oldBackend = settings.verificationBackends.find(value => value.name == oldBackendName) +// val newBackend = settings.verificationBackends.find(value => value.name == newBackendName) +// +// if (oldBackend && newBackend && oldBackend.engine.toLowerCase() == 'viperserver' && newBackend.engine.toLowerCase() == 'viperserver') +// return false +// +// return true +// } + + def makeSureFileExistsAndCheckForWritePermission(filePath: String, firstTry = true): Future[any] = { + return new Future((resolve, reject) => { + try { + val folder = pathHelper.dirname(filePath) + mkdirp(folder, (err) => { + if (err && err.code != 'EEXIST') { + resolve(err.code + ": Error creating " + folder + " " + err.message) + } else { + fs.open(filePath, 'a', (err, file) => { + if (err) { + resolve(err.code + ": Error opening " + filePath + " " + err.message) + } else { + fs.close(file, err => { + if (err) { + resolve(err.code + ": Error closing " + filePath + " " + err.message) + } else { + fs.access(filePath, 2, (e) => { //fs.constants.W_OK is 2 + if (e) { + resolve(e.code + ": Error accessing " + filePath + " " + e.message) + } else { + resolve(null) + } + }) + } + }) + } + }) + } + }) + } catch (e) { + resolve(e) + } + }) + } + + def extract(filePath: String): Future[Boolean] = { + return new Future((resolve, reject) => { + try { + //extract files + Log.log("Extracting files...", LogLevel.Info) + Log.startProgress() + val unzipper = new DecompressZip(filePath) + + unzipper.on('progress', function (fileIndex, fileCount) { + Log.progress("Extracting Viper Tools", fileIndex + 1, fileCount, LogLevel.Debug) + }) + + unzipper.on('error', function (e) { + if (e.code && e.code == 'ENOENT') { + Log.debug("Error updating the Viper Tools, missing create file permission in the viper tools directory: " + e) + } else if (e.code && e.code == 'EACCES') { + Log.debug("Error extracting " + filePath + ": " + e + " | " + e.message) + } else { + Log.debug("Error extracting " + filePath + ": " + e) + } + resolve(false) + }) + + unzipper.on('extract', function (log) { + resolve(true) + }) + + unzipper.extract({ + path: pathHelper.dirname(filePath), + filter: function (file) { + return file.type !== "SymbolicLink" + } + }) + } catch (e) { + Log.debug("Error extracting viper tools: " + e) + resolve(false) + } + }) + } + + def getParentDir(fileOrFolderPath: String): String = { + if (!fileOrFolderPath) return null + val obj = pathHelper.parse(fileOrFolderPath) + if (obj.base) { + return obj.dir + } + val folderPath = obj.dir + val is_matching = folderPath.match(/(^.*)[\/\\].+$/) //the regex retrieves the parent directory + if (is_matching) { + if (is_matching[1] == fileOrFolderPath) { + Log.debug("getParentDir has a fixpoint at " + fileOrFolderPath) + return null + } + return match[1] + } + else { + return null + } + } + + + + + +} \ No newline at end of file diff --git a/src/main/scala/viper/server/Coordinator.scala b/src/main/scala/viper/server/Coordinator.scala new file mode 100644 index 0000000..3d83c97 --- /dev/null +++ b/src/main/scala/viper/server/Coordinator.scala @@ -0,0 +1,75 @@ +package viper.server + +import java.util.concurrent.CompletableFuture + +import org.eclipse.lsp4j.services.LanguageClient +import org.eclipse.lsp4j.{Position, TextDocumentItem, Range} + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.util.matching.Regex + +object Coordinator { + var port: Int = _ + var url: String = _ + var client: IdeLanguageClient = _ + + var backend: Backend + var tempDirectory: String = pathHelper.join(os.tmpdir(), ".vscode") = _ + var backendOutputDirectory: String = os.tmpdir() = _ + var executedStages: ArrayBuffer[Stage] = _ + var documents: TextDocumentItem = new TextDocumentItem() + var verificationTasks = mutable.Map.empty[String, VerificationTask] + var backendService: ViperServerService = _ + var startingOrRestarting: Boolean = false + + def getAddress: String = url + ":" + port + + def stage: Option[Stage] = { + if (executedStages != null && this.executedStages.length > 0) { + Some(executedStages(executedStages.length - 1)) + } else { + None + } + } + + def canVerificationBeStarted(uri: String, manuallyTriggered: Boolean): Boolean = { + //check if there is already a verification task for that file + if(verificationTasks.get(uri).isEmpty){ + Log.debug("No verification task found for file: " + uri) + false + } else if (!backendService.isReady) { + if (manuallyTriggered) { + Log.hint("The verification backend is not ready yet") + } + Log.debug("The verification backend is not ready yet") + false + } else { + true + } + } + + def stopAllRunningVerifications(): CompletableFuture[Void] = { + if (Coordinator.verificationTasks.nonEmpty) { + val promises = Coordinator.verificationTasks.values.map(task => task.abortVerification()).toSeq + CompletableFuture.allOf(promises:_*) + } else { + CompletableFuture.completedFuture() + } + } + + //Communication requests and notifications sent to language client + def sendStateChangeNotification(params: StateChangeParams, task: Option[VerificationTask]): Unit = { + if (task.isDefined) task.get.state = params.newState + client.notifyStateChanged(params) + } + + def sendStepsAsDecorationOptions(decorations: StepsAsDecorationOptionsResult) = { + Log.log("Update the decoration options (" + decorations.decorationOptions.length + ")", LogLevel.Debug) + client.stepsAsDecorationOptions(decorations) + } + + def sendStartBackendMessage(backend: String, forceRestart: Boolean, isViperServer: Boolean) { + client.sendNotification(Commands.StartBackend, {backend: backend, forceRestart: forceRestart, isViperServer: isViperServer }) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/DataProtocol.scala b/src/main/scala/viper/server/DataProtocol.scala new file mode 100644 index 0000000..b5ef69c --- /dev/null +++ b/src/main/scala/viper/server/DataProtocol.scala @@ -0,0 +1,199 @@ +package viper.server + +import org.eclipse.lsp4j.{Range, Position} + +object VerificationSuccess extends Enumeration { + type VerificationSuccess = Value + + val NA, Success, ParsingFailed, TypecheckingFailed = Value + val VerificationFailed = Value // Manually aborted verification + val Aborted = Value // Caused by internal error + val Error = Value // Caused by veification taking too long + val Timeout = Value +} +import VerificationSuccess._ + +object VerificationState extends Enumeration { + type VerificationState = Value + + val Stopped, Starting = Value + val VerificationRunning, VerificationPrintingHelp, VerificationReporting = Value + val PostProcessing = Value + val Ready = Value + val Stopping = Value + val Stage = Value +} +import VerificationState._ + +object SettingsErrorType extends Enumeration { + type SettingsErrorType = Value + + val Error, Warning = Value +} +import SettingsErrorType._ + +object LogLevel extends Enumeration { + type LogLevel = Value + + val None = Value // No output + val Default = Value // Only verification specific output + val Info = Value // Some info about internal state, critical errors + val Verbose = Value // More info about internal state + val Debug = Value // Detailed information about internal state, non critical errors + val LowLevelDebug = Value // all output of used tools is written to logFile, +} // some of it also to the console + +case class Progress ( + domain: String, + current: Double, + total: Double, + progress: Double, + postfix: Double) + +case class Hint(msg: String, showButton1: Boolean, showButton2: Boolean) + +case class StateChangeParams( + newState: VerificationState, + progress: Double = null, + success: VerificationSuccess = null, + verificationCompleted: Boolean = null, + manuallyTriggered: Boolean = null, + filename: String = null, + backendName: String = null, + time: Int = null, + nofErrors: Int = null, + verificationNeeded: Boolean = null, + uri: String = null, + stage: String = null, + error: String = null, + diagnostics: String = null) + +case class Backend( + name: String, + backend_type: String, + paths: Array[String], + engine: String, + timeout: Int, + stages: Array[Stage], + stoppingTimeout: Int, + version: String) extends VersionedSettings + +case class VerifyRequest ( + uri: String, // file to verify + manuallyTriggered: Boolean, // was the verification triggered manually + workspace: String) // the path to the open workspace folder + +case class SettingsError (errorType: SettingsErrorType, msg: String) + +trait VersionedSettings { + val version: String +} + +//TODO Care that serverJars can take other types. These must be translated to a String. +case class ViperServerSettings( + serverJars: Array[String], // Locator to the ViperServer jars + customArguments: String, // custom commandLine arguments + backendSpecificCache: Boolean, // it set to false, cached errors are reused across backends + disableCaching: Boolean, // disable the caching mechanism + timeout: Int, // After timeout ms the startup of the viperServer is expected to have failed and thus aborted + viperServerPolicy: String, // Specifies whether ViperServer should be started by the IDE or whether the IDE should attach to an existing instance of ViperServer. Possible values: "attach", "create". + viperServerAddress: String, // Specifies the address part of the URL that ViperServer is running on. + viperServerPort: Int, // Specifies the port part of the URL that ViperServer is running on. Only needed if viperServerPolicy is set to 'attach'. + version: String ) extends VersionedSettings + +case class Versions( + viperServerSettingsVersion: String, + backendSettingsVersion: String, + pathSettingsVersion: String, + userPreferencesVersion: String, + javaSettingsVersion: String, + advancedFeaturesVersion: String, + defaultSettings: AnyRef, + extensionVersion: String) + +case class ViperSettings( + viperServerSettings: ViperServerSettings, // All viperServer related settings + verificationBackends: Array[Backend], // Description of backends + paths: PathSettings, // Used paths + preferences: UserPreferences, // General user preferences + javaSettings: JavaSettings, // Java settings + advancedFeatures: AdvancedFeatureSettings) // Settings for AdvancedFeatures + +case class Stage( + name: String, //The per backend unique name of this stage + isVerification: Boolean, //Enable if this stage is describing a verification + mainMethod: String, //The method to invoke when staring the stage + customArguments: String, //the commandline arguments for the java engine + onParsingError: String, //The name of the stage to start in case of a parsing error + onTypeCheckingError: String, //The name of the stage to start in case of a type checking error + onVerificationError: String, //The name of the stage to start in case of a verification error + onSuccess: String) //The name of the stage to start in case of a success + +case class PathSettings( + viperToolsPath: Either[String, PlatformDependentPath], // Path to the folder containing all the ViperTools + z3Executable: Either[String, PlatformDependentPath], // The path to the z3 executable + boogieExecutable: Either[String, PlatformDependentPath], // The path to the boogie executable + version: String) extends VersionedSettings + + +case class PlatformDependentPath ( + windows: Option[String], + mac: Option[String], + linux: Option[String]) + +case class PlatformDependentURL ( + windows: Option[String], + mac: Option[String], + linux: Option[String]) + +case class UserPreferences ( + autoSave: Boolean, //Enable automatically saving modified viper files + logLevel: Int, //Verbosity of the output, all output is written to the logFile, regardless of the logLevel + autoVerifyAfterBackendChange: Boolean, //Reverify the open viper file upon backend change. + showProgress: Boolean, //Display the verification progress in the status bar. Only useful if the backend supports progress reporting. + viperToolsProvider: Either[String, PlatformDependentURL], //The URL for downloading the ViperTools from + version: String) extends VersionedSettings + +//The arguments used for all java invocations +case class JavaSettings(customArguments: String, version: String) extends VersionedSettings + +case class AdvancedFeatureSettings( + enabled: Boolean, //Enable heap visualization, stepwise debugging and execution path visualization + showSymbolicState: Boolean, //Show the symbolic values in the heap visualization. If disabled, the symbolic values are only shown in the error states. + darkGraphs: Boolean, //To get the best visual heap representation, this setting should match with the active theme. + simpleMode: Boolean, //Useful for verifying programs. Disable when developing the backend + showOldState: Boolean, //Visualize also the oldHeap in the heap preview + showPartialExecutionTree: Boolean, //Show the part of the execution tree around the current state in the state visualization + verificationBufferSize: Int, //Maximal buffer size for verification in KB + compareStates: Boolean, + version: String) extends VersionedSettings + + +case class Definition(definition_type: String, name: String, code_location: Position, scope: Range) + +object BackendOutputType { + val Start = "Start" + val End = "End" + val VerificationStart = "VerificationStart" + val MethodVerified = "MethodVerified" + val FunctionVerified = "FunctionVerified" + val PredicateVerified = "PredicateVerified" + val Error = "Error" + val Outline = "Outline" + val Definitions = "Definitions" + val Success = "Success" + val Stopped = "Stopped" +} + +case class BackendOutput( + typ: String, + name: String = null, + backendType: String = null, + nofMethods: Int = null, + nofPredicates: Int = null, + nofFunctions: Int = null, //for End + time: Long = null, //for Error + file: String = null, + errors: Array[Error] = null, //for Outlin + members: Array[Member] = null, //for Definitions + definitions: Array[Definition] = null) \ No newline at end of file diff --git a/src/main/scala/viper/server/IdeLanguageClient.scala b/src/main/scala/viper/server/IdeLanguageClient.scala index 8d8fc38..37ea26f 100644 --- a/src/main/scala/viper/server/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/IdeLanguageClient.scala @@ -1,11 +1,48 @@ package viper.server +import java.util.concurrent.CompletableFuture + +import org.eclipse.lsp4j.{Diagnostic, Position} import org.eclipse.lsp4j.services.LanguageClient -import org.eclipse.lsp4j.jsonrpc.services.JsonNotification +import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} trait IdeLanguageClient extends LanguageClient { -// @JsonNotification("gobraServer/noVerificationInformation") + @JsonNotification(S2C_Commands.FileOpened) + def notifyFileOpened(uri: String): Unit + + @JsonNotification(S2C_Commands.FileClosed) + def notifyFileClosed(uri: String): Unit + + @JsonRequest(C2S_Commands.GetIdentifier) + def requestIdentifier(pos: Position): CompletableFuture[String] + + @JsonRequest(C2S_Commands.GetViperFileEndings) + def requestVprFileEndings(): CompletableFuture[Array[String]] + + @JsonNotification(S2C_Commands.BackendReady) + def notifyBackendReady(uri: String) + + @JsonNotification(S2C_Commands.BackendChange) + def notifyBackendChanged(name: String) + + @JsonNotification(S2C_Commands.Progress) + def notifyProgress(progress: Progress, logLevel: Int) + + @JsonNotification(S2C_Commands.Log) + def notifyLog(msg: String, logLevel: Int) + + @JsonNotification(S2C_Commands.Hint) + def notifyHint(msg: String, logLevel: Int) + + @JsonNotification(S2C_Commands.VerificationNotStarted) + def notifyVerificationNotStarted(uri: String) + + @JsonNotification(S2C_Commands.StateChange) + def notifyStateChanged(params: StateChangeParams) + + + // @JsonNotification("gobraServer/noVerificationInformation") // def noVerificationInformation(): Unit // // @JsonNotification("gobraServer/overallResult") diff --git a/src/main/scala/viper/server/IdeLog.scala b/src/main/scala/viper/server/IdeLog.scala new file mode 100644 index 0000000..69899f4 --- /dev/null +++ b/src/main/scala/viper/server/IdeLog.scala @@ -0,0 +1,56 @@ +package viper.server + +import LogLevel._ + +object Log { + def log(message: String, logLevel: LogLevel) = { + Coordinator.client.notifyLog(message, logLevel.id) + } + + def toLogFile(message: String) = log(message, Default) + + def debug(message: String) = log(message, Debug) + + def debug(message: String, error: Throwable) = log(message, Debug) + + def info(message: String) = log(message, Info) + + def lowLevel(message: String) = log(message, LowLevelDebug) + + private var lastProgress: Double = _ + + def startProgress() = { + lastProgress = 0 + } + + def progress(domain: String, cur: Double, len: Double, logLevel: LogLevel) = { + val progress = 100.0 * cur / len + if (Math.floor(progress) > lastProgress) { + lastProgress = progress + val data = Progress(domain, cur, len, progress, null) + Coordinator.client.notifyProgress(data, logLevel.id) + } + } + + def logWithOrigin(origin: String, message: String, logLevel: LogLevel) = { + if (message != null) { + val indicator: String = if (logLevel >= Debug) "[" + origin + "]: " else "" + val msg_with_origin: String = indicator + message + log(msg_with_origin, logLevel) + } + } + +// -> ?? +// def logOutput(process: child_process.ChildProcess, label: String) = { +// process.stdout.on('data', (data: String) => { +// Log.logWithOrigin(label, data, LogLevel.LowLevelDebug) +// }) +// process.stdout.on('data', (data: String) => { +// Log.logWithOrigin(label + " error", data, LogLevel.LowLevelDebug) +// }) +// } + + def hint(message: String, showSettingsButton: Boolean = false, showViperToolsUpdateButton: Boolean = false) = { + Coordinator.client.notifyHint(S2C_Commands.Hint, Hint(message, showSettingsButton, showViperToolsUpdateButton )) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/LanguageServerReceiver.scala b/src/main/scala/viper/server/LanguageServerReceiver.scala new file mode 100644 index 0000000..38f86b9 --- /dev/null +++ b/src/main/scala/viper/server/LanguageServerReceiver.scala @@ -0,0 +1,217 @@ +package viper.server + +import LogLevel._ +import java.util.concurrent.{CompletableFuture => CFuture} + +import scala.collection.JavaConverters._ +import com.google.gson.JsonPrimitive +import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} +import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionOptions, CompletionParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, MessageParams, MessageType, Range, ServerCapabilities, ShowMessageRequestParams, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} +import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware} + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + +class LanguageServerReceiver extends LanguageClientAware { + + @JsonRequest(value = "initialize") + def initialize(params: InitializeParams): CFuture[InitializeResult] = { + println("initialize") + val capabilities = new ServerCapabilities() + // OG + // always send full text document for each notification: + // capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) + // capabilities.setCompletionProvider(new CompletionOptions(true, null)) + capabilities.setDefinitionProvider(true) + capabilities.setDocumentSymbolProvider(true) + CFuture.completedFuture(new InitializeResult(capabilities)) + } + + @JsonNotification("textDocument/didChange") + def onDidOpenDocument(params: DidOpenTextDocumentParams): Unit = { + try { + val uri = params.getTextDocument.getUri + Common.isViperSourceFile(uri).thenAccept(isViperFile => { + if (isViperFile) { + //notify client; + Coordinator.client.notifyFileOpened(uri) + if (!Coordinator.verificationTasks.contains(uri)) { + //create new task for opened file + val task = new VerificationTask(uri) + Coordinator.verificationTasks += (uri -> task) + } + } + }) + } catch { + case e: Throwable => Log.debug("Error handling TextDocument opened") + } + } + + @JsonNotification("textDocument/didChange") + def onDidChangeDocument(params: DidChangeTextDocumentParams): Unit = { + val task_opt = Coordinator.verificationTasks.get(params.getTextDocument.getUri) + val task: VerificationTask = task_opt.getOrElse(return) + task.symbolInformation = ArrayBuffer() + task.definitions = ArrayBuffer() + } + + @JsonNotification("textDocument/didClose") + def onDidCloseDocument(params: DidCloseTextDocumentParams): Unit = { + try { + val uri = params.getTextDocument.getUri + Common.isViperSourceFile(uri).thenAccept(isViperFile => { + if (isViperFile) Coordinator.client.notifyFileClosed(uri) + }) + } catch { + case e: Throwable => Log.debug("Error handling TextDocument opened") + } + } + + @JsonNotification(S2C_Commands.FileClosed) + def onFileClosed(uri: String): Unit = { + val task_opt = Coordinator.verificationTasks.get(uri) + val task = task_opt.getOrElse(return) + task.resetDiagnostics() + Coordinator.verificationTasks -= uri + } + + @JsonRequest("textDocument/documentSymbol") + def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[SymbolInformation] = { + val task_opt = Coordinator.verificationTasks.get(params.getTextDocument.getUri) + val task = task_opt.getOrElse(return CFuture.completedFuture(Array())) + CFuture.completedFuture(task.symbolInformation) + } + + @JsonRequest("textDocument/definition") + def onGetDefinition(params: TextDocumentPositionParams): CFuture[List[Location]] = { + Log.log("Handling definitions request for params: " + params.toString, LogLevel.Debug) + val document = params.getTextDocument + val pos = params.getPosition + val task_opt = Coordinator.verificationTasks.get(params.getTextDocument.getUri) + + task_opt match { + case Some(task) => + Log.log("Found verification task for URI " + document.getUri, LogLevel.LowLevelDebug) + Coordinator.client.requestIdentifier(pos).thenApply(word => { + Log.log("Got word: " + word, LowLevelDebug) + task.definitions.filter(d => (d.scope.getStart == null) //is global + || (Common.comparePosition(d.scope.getStart, pos) <= 0 + && Common.comparePosition(d.scope.getEnd, pos) >= 0)) // in scope + .filter(d => word == d.name) + .map(d => new Location(document.getUri, new Range(d.code_location, d.code_location))) + .toList + }) + case None => + // No definition found - maybe it's a keyword. + val e = s"Verification task not found for URI ${document.getUri}" + Log.debug(e) + CFuture.failedFuture(new Throwable(e)) // needs to return some CF. + } + } + + @JsonRequest(C2S_Commands.RemoveDiagnostics) + def onRemoveDiagnostics(uri: String): CFuture[Boolean] = { + val task_opt = Coordinator.verificationTasks.get(uri) + val task = task_opt.getOrElse(return CFuture.completedFuture(false)) + task.resetDiagnostics() + CFuture.completedFuture(true) + } + + @JsonRequest("GetLanguageServerUrl") + def onGetServerUrl(): CFuture[String] = { + CFuture.completedFuture(Coordinator.getAddress()) + } + + @JsonNotification(C2S_Commands.SwapBackend) + def onSwapBackend(backendName: String): Unit = { + try { + Coordinator.backendService.swapBackend(Settings.getBackend(backendName)); + } catch { + case e: Throwable => Log.debug("Error handling swap backend request: " + e); + } + } + + @JsonNotification(C2S_Commands.Verify) + def onVerify(data: VerifyRequest): Unit = { + //it does not make sense to reverify if no changes were made and the verification is already running + if (Coordinator.canVerificationBeStarted(data.uri, data.manuallyTriggered)) { + Settings.workspace = data.workspace + Log.log("start or restart verification", LogLevel.Info); + //stop all other verifications because the backend crashes if multiple verifications are run in parallel + Coordinator.stopAllRunningVerifications().thenAccept(_ => { + //start verification + Coordinator.executedStages = ArrayBuffer() + val hasVerificationstarted = Coordinator.verificationTasks + .getOrElse(data.uri, return) + .verify(data.manuallyTriggered) + if (!hasVerificationstarted) { + Coordinator.client.notifyVerificationNotStarted(data.uri) + } + }).exceptionally(_ => { + Log.debug("Error handling verify request") + Coordinator.client.notifyVerificationNotStarted(data.uri) + null //java void + }) + } else { + Log.log("The verification cannot be started.", LogLevel.Info) + Coordinator.client.notifyVerificationNotStarted(data.uri) + } + } + + @JsonNotification(C2S_Commands.FlushCache) + def flushCache(file: String): Unit = { + println("flushing cache...") +// _client match { +// case Some(c) => c.showMessage(new MessageParams(MessageType.Info, s"$file got flushed :>")) +// case _ => +// } + } + + @JsonRequest(value = "shutdown") + def shutdown(): CFuture[AnyRef] = { + println("shutdown") + CFuture.completedFuture(null) + } + + @JsonNotification(value = "exit") + def exit(): Unit = { + println("exit") + sys.exit() + } + + @JsonRequest("textDocument/completion") + def completion(params: CompletionParams): CFuture[CompletionList] = { + val tsItem = new CompletionItem("TypeScript") + tsItem.setKind(CompletionItemKind.Text) + tsItem.setData(1) + val jsItem = new CompletionItem("JavaScript") + jsItem.setKind(CompletionItemKind.Text) + jsItem.setData(2) + val completions = new CompletionList(List(tsItem, jsItem).asJava) + CFuture.completedFuture(completions) + } + + @JsonRequest("completionItem/resolve") + def completionItemResolve(item: CompletionItem): CFuture[CompletionItem] = { + val data: Object = item.getData + data match { + case n: JsonPrimitive if n.getAsInt == 1 => { + item.setDetail("TypeScript details") + item.setDocumentation("TypeScript documentation") + } + case n: JsonPrimitive if n.getAsInt == 2 => { + item.setDetail("JavaScript details") + item.setDocumentation("JavaScript documentation") + } + case _ => item.setDetail(s"${data.toString} is instance of ${data.getClass}") + } + CFuture.completedFuture(item) + } + + override def connect(client: LanguageClient): Unit = { + println("Connecting plugin client.") + val c = client.asInstanceOf[IdeLanguageClient] + Coordinator.client = c + } +} diff --git a/src/main/scala/viper/server/Progress.scala b/src/main/scala/viper/server/Progress.scala new file mode 100644 index 0000000..d8234c0 --- /dev/null +++ b/src/main/scala/viper/server/Progress.scala @@ -0,0 +1,33 @@ +package viper.server + +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2019 ETH Zurich. + */ +class Progress(val nofPredicates: Int, val nofFunctions: Int, val nofMethods: Int) { + + var currentFunctions = 0; + var currentMethods = 0; + var currentPredicates = 0; + + def updateProgress(output: BackendOutput) = { + try { + output.typ match { + case BackendOutputType.MethodVerified => currentMethods += 1 + case BackendOutputType.FunctionVerified => currentFunctions += 1 + case BackendOutputType.PredicateVerified => currentPredicates += 1 + } + } catch { + case e: Throwable => Log.debug("Error updating progress: ", e); + } + } + + def toPercent(): Double = { + val total = nofFunctions + nofMethods + nofPredicates + val current = currentFunctions + currentMethods + currentPredicates + 100 * current / total + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/Settings.scala b/src/main/scala/viper/server/Settings.scala new file mode 100644 index 0000000..7c1f009 --- /dev/null +++ b/src/main/scala/viper/server/Settings.scala @@ -0,0 +1,891 @@ +package viper.server + +import java.util.concurrent.CompletableFuture + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + +case class ResolvedPath (path: String, exists: Boolean, error: Option[String]) + +object Settings { + implicit val ex = ExecutionContext.global + var settings: ViperSettings + var isWin = System.getProperties.get("os.name") + var isLinux = /^linux/.test(process.platform) + var isMac = /^darwin/.test(process.platform) + var workspace: String = _ + var VERIFY = "verify" + var selectedBackend: String + + private var firstSettingsCheck = true + + private var _valid: Boolean = false + private var _errors: Array[SettingsError] = Array() + private var _upToDate: Boolean = false + + private var home = os.homedir(); + + def getStage(backend: Backend, name: Option[String]): Option[Stage] = { + for { + n <- name + res <- backend.stages.find(_.name == n) + } yield res + } + + def getStageFromSuccess(backend: Backend, stage: Stage, success: Success): Stage = { + switch (success) { + case VerificationSuccess.ParsingFailed: + return this.getStage(backend, stage.onParsingError); + case VerificationSuccess.VerificationFailed: + return this.getStage(backend, stage.onVerificationError); + case VerificationSuccess.TypecheckingFailed: + return this.getStage(backend, stage.onTypeCheckingError); + case VerificationSuccess.Success: + return this.getStage(backend, stage.onSuccess); + } + return null; + } + + def backendEquals(a: Backend, b: Backend): Boolean = { + var areEqual: Boolean = a.stages.length == b.stages.length + areEqual = areEqual && a.name == b.name + areEqual = areEqual && a.backend_type == b.backend_type + areEqual = areEqual && a.timeout == b.timeout + areEqual = areEqual && this.resolveEngine(a.engine) == this.resolveEngine(b.engine); + a.stages.forEach((element, i) => { + areEqual = areEqual && this.stageEquals(element, b.stages[i]); + }); + areEqual = areEqual && a.paths.length == b.paths.length; + for (var i = 0; i < a.paths.length; i++) { + areEqual = areEqual && a.paths[i] == b.paths[i]; + } + return areEqual; + } + + def backendEquals(one: Option[Backend], other: Option[Backend]): Option[Boolean] = { + for { + a <- one + b <- other + } yield backendEquals(a, b) + } + + private def resolveEngine(engine: String) { + if (engine && (engine.toLowerCase() == "viperserver")) { + return engine; + } else { + return "none"; + } + } + + def useViperServer(backend: Backend) { + if (!backend || !backend.engine) return false; + return backend.engine.toLowerCase() == "viperserver"; + } + + private def stageEquals(a: Stage, b: Stage): Boolean { + var same = a.customArguments == b.customArguments; + same = same && a.mainMethod == b.mainMethod; + same = same && a.name == b.name; + same = same && a.isVerification == b.isVerification; + same = same && a.onParsingError == b.onParsingError; + same = same && a.onTypeCheckingError == b.onTypeCheckingError; + same = same && a.onVerificationError == b.onVerificationError; + same = same && a.onSuccess == b.onSuccess; + return same; + } + + def expandCustomArguments(args: String, stage: Stage, fileToVerify: String, backend: Backend): String { + //Log.log("Command before expanding: " + args,LogLevel.LowLevelDebug); + args = args.replace(/\s+/g, ' '); //remove multiple spaces + args = args.replace(/\$z3Exe\$/g, '"' + this.settings.paths.z3Executable + '"'); + args = args.replace(/\$boogieExe\$/g, '"' + this.settings.paths.boogieExecutable + '"'); + args = args.replace(/\$mainMethod\$/g, stage.mainMethod); + args = args.replace(/\$backendPaths\$/g, Settings.backendJars(backend)); + args = args.replace(/\$disableCaching\$/g, (Settings.settings.viperServerSettings.disableCaching === true ? "--disableCaching" : "")); + args = args.replace(/\$fileToVerify\$/g, '"' + fileToVerify + '"'); + args = args.replace(/\s+/g, ' '); //remove multiple spaces + //Log.log("Command after expanding: " + args.trim(),LogLevel.LowLevelDebug); + + return args.trim(); + } + + def expandViperToolsPath(path: String): String = { + if (!path) return path + if (typeof Settings.settings.paths.viperToolsPath !== "String") { + return path + } +// path = path.replace(/\$viperTools\$/g, [String>Settings.settings.paths.viperToolsPath) + return path + } + + def selectBackend(settings: ViperSettings, selectedBackend: String): Backend = { + if (selectedBackend != null) { + Settings.selectedBackend = selectedBackend; + } + if (!settings || !settings.verificationBackends || settings.verificationBackends.length == 0) { + this.selectedBackend = null; + return null; + } + if (this.selectedBackend) { +// for (var i = 0; i < settings.verificationBackends.length; i++) { +// var backend = settings.verificationBackends[i]; +// if (backend.name === this.selectedBackend) { +// return backend; +// } +// } + } + this.selectedBackend = settings.verificationBackends[0].name; + return settings.verificationBackends[0]; + } + + def getBackendNames(settings: ViperSettings): Array[String] { + var backendNames = []; + settings.verificationBackends.forEach((backend) => { + backendNames.push(backend.name); + }) + return backendNames; + } + + def getBackend(backendName: String): Backend { + return Settings.settings.verificationBackends.find(b => { return b.name == backendName }); + } + + def valid(): Boolean { + LanguageServerState.sendSettingsCheckedNotification({ ok: this._valid, errors: this._errors, settings: this.settings }); + return this._valid; + } + + def upToDate(): Boolean { + return this._upToDate; + } + + private def viperServerRelatedSettingsChanged(oldSettings: ViperSettings) { + if (!oldSettings) return true; + if ((Array[[String]]oldSettings.viperServerSettings.serverJars).length != (Array[[String]]this.settings.viperServerSettings.serverJars).length) + return true; + (Array[[String]]oldSettings.viperServerSettings.serverJars).forEach((path, index) => { + if (path != (Array[[String]]this.settings.viperServerSettings.serverJars)[index]) { + return true; + } + }) + if (oldSettings.viperServerSettings.backendSpecificCache != this.settings.viperServerSettings.backendSpecificCache + || oldSettings.viperServerSettings.customArguments != this.settings.viperServerSettings.customArguments + //|| oldSettings.viperServerSettings.disableCaching != this.settings.viperServerSettings.disableCaching //no need to restart the ViperServer if only that changes + || oldSettings.viperServerSettings.timeout != this.settings.viperServerSettings.timeout + ) { + return true; + } + Log.log("ViperServer settings did not change", LogLevel.LowLevelDebug); + return false; + } + + //tries to restart backend, + def initiateBackendRestartIfNeeded(oldSettings?: ViperSettings, selectedBackend?: String, viperToolsUpdated: Boolean = false) { + Settings.checkSettings(viperToolsUpdated).then(() => { + if (Settings.valid()) { + var newBackend = Settings.selectBackend(Settings.settings, selectedBackend); + + if (newBackend) { + //only restart the backend after settings changed if the active backend was affected + + Log.log("check if restart needed", LogLevel.LowLevelDebug); + var backendChanged = !Settings.backendEquals(Coordinator.backend, newBackend) //change in backend + var mustRestartBackend = !Coordinator.backendService.isReady() //backend is not ready -> restart + || viperToolsUpdated //Viper Tools Update might have modified the binaries + || (Coordinator.backendService.isViperServerService != this.useViperServer(newBackend)) //the new backend requires another engine type + || (Settings.useViperServer(newBackend) && this.viperServerRelatedSettingsChanged(oldSettings)) // the viperServerSettings changed + if (mustRestartBackend || backendChanged) { + Log.log(`Change Backend: from ${LanguageServerState.backend ? LanguageServerState.backend.name : "No Backend"} to ${newBackend ? newBackend.name : "No Backend"}`, LogLevel.Info); + Coordinator.backend = newBackend; + Coordinator.verificationTasks.forEach(task => task.resetLastSuccess()); + Coordinator.sendStartBackendMessage(Coordinator.backend.name, mustRestartBackend, Settings.useViperServer(newBackend)); + } else { + Log.log("No need to restart backend. It is still the same", LogLevel.Debug) + Coordinator.backend = newBackend; + Coordinator.sendBackendReadyNotification({ + name: Coordinator.backend.name, + restarted: false, + isViperServer: Settings.useViperServer(newBackend) + }); + } + } else { + Log.debug("No backend, even though the setting check succeeded."); + } + } else { + Coordinator.backendService.stop(); + } + }); + } + + private def addError(msg: String) { + this._errors.push({ type: SettingsErrorType.Error, msg: msg }); + } + private def addErrors(errors: SettingsError[]) { + this._errors = this._errors.concat(errors); + } + private def addWarning(msg: String) { + this._errors.push({ type: SettingsErrorType.Warning, msg: msg }); + } + +// private def checkSettingsVersion(settings: AnyRef, requiredVersions: AnyRef): Array[String] = { +// var oldSettings = Array() +// //check the settings versions +// if (!requiredVersions) { +// Log.error("Getting required version failed."); +// } else { +// if (Version.createFromVersion(requiredVersions.advancedFeaturesVersion).compare(Version.createFromHash(settings.advancedFeatures.v)) > 0) { +// oldSettings.push("advancedFeatures"); +// } +// if (Version.createFromVersion(requiredVersions.javaSettingsVersion).compare(Version.createFromHash(settings.javaSettings.v)) > 0) { +// oldSettings.push("javaSettings"); +// } +// if (Version.createFromVersion(requiredVersions.viperServerSettingsVersion).compare(Version.createFromHash(settings.viperServerSettings.v)) > 0) { +// oldSettings.push("viperServerSettings"); +// } +// if (Version.createFromVersion(requiredVersions.pathSettingsVersion).compare(Version.createFromHash(settings.paths.v)) > 0) { +// oldSettings.push("paths"); +// } +// if (Version.createFromVersion(requiredVersions.userPreferencesVersion).compare(Version.createFromHash(settings.preferences.v)) > 0) { +// oldSettings.push("preferences"); +// } +// var requiredBackendVersion = Version.createFromVersion(requiredVersions.backendSettingsVersion); +// settings.verificationBackends.forEach(backend => { +// if (requiredBackendVersion.compare(Version.createFromHash(backend.v)) > 0) { +// oldSettings.push("backend " + backend.name); +// } +// }); +// } +// return oldSettings; +// } + + + +// def checkSettings(viperToolsUpdated: Boolean): Future[Boolean] = { +// try { +// _valid = false; +// _errors = Array(); +// _upToDate = false; +// +// def getSettingErrorFuture: Future[Array[SettingsError]] = Future { +// val cf = LanguageServerState.client.checkIfSettingsVersionsSpecified() +// val res = cf.join() +// res +// } +// +// def hasErrors(errors: Array[SettingsError]): Boolean = { +// if (errors.nonEmpty) { +// errors.foreach(addErrors) +// true +// } else { +// false +// } +// } +// +// def getVersionsFuture(errors: Array[SettingsError]): Future[Option[Versions]] = { +// if(hasErrors(errors)) { +// Future { +// None +// } +// } else { +// Future { +// val cf = LanguageServerState.client.requestRequiredVersion() +// val res = cf.join() +// Some(res) +// } +// } +// } +// +// def useVersions(versions: Option[Versions]): Future[Boolean] = { +// Future { +// case None => +// false +// case Some(v) => +// if (firstSettingsCheck) { +// Log.log("Extension Version: " + v.extensionVersion + " - " + Version.hash(requiredVersions.extensionVersion), LogLevel.LowLevelDebug) +// firstSettingsCheck = false; +// } +// var settings = Settings.settings; +// var oldSettings: Array[String] = this.checkSettingsVersion(settings, v); +// var defaultSettings = v.defaultSettings; +// +// if (oldSettings.nonEmpty) { +// var affectedSettings = oldSettings.length < 10 ? "(" + oldSettings.join(", ") + ")": "(" + oldSettings.length + ")"; +// addError("Old viper settings detected: " + affectedSettings + " please replace the old settings with the new default settings."); +// false +// } else { +// _upToDate = true; +// //Check viperToolsProvider +// settings.preferences.viperToolsProvider = this.checkPlatformDependentUrl(settings.preferences.viperToolsProvider); +// +// //Check Paths +// //check viperToolsPath +// var resolvedPath: ResolvedPath = this.checkPath(settings.paths.viperToolsPath, "Path to Viper Tools:", false, true, true); +// settings.paths.viperToolsPath = resolvedPath.path; +// if (!resolvedPath.exists) { +// if (!viperToolsUpdated) { +// //Automatically install the Viper tools +// LanguageServerState.updateViperTools(true); +// reject(); // in this case we do not want to continue restarting the backend, +// //the backend will be restarted after the update +// } else { +// resolve(false); +// } +// return; +// } +// +// //check z3 Executable +// settings.paths.z3Executable = this.checkPath(settings.paths.z3Executable, "z3 Executable:", true, true, true).path; +// //check boogie executable +// settings.paths.boogieExecutable = this.checkPath(settings.paths.boogieExecutable, `Boogie Executable: (If you don't need boogie, set it to "")`, true, true, true).path; +// +// //check backends +// if (!settings.verificationBackends || settings.verificationBackends.length == 0) { +// settings.verificationBackends = defaultSettings["viperSettings.verificationBackends"].default; +// } else { +// defaultSettings["viperSettings.verificationBackends"].default.forEach(defaultBackend => { +// var customBackend = settings.verificationBackends.filter(backend => backend.name == defaultBackend.name)[0]; +// if (customBackend) { +// //Merge the backend with the default backend +// this.mergeBackend(customBackend, defaultBackend); +// } else { +// //Add the default backend if there is none with the same name +// settings.verificationBackends.push(defaultBackend); +// } +// }) +// } +// Settings.checkBackends(settings.verificationBackends); +// +// //check ViperServer related settings +// var viperServerRequired = settings.verificationBackends.some(elem => this.useViperServer(elem)); +// if (viperServerRequired) { +// //check viperServer path +// settings.viperServerSettings.serverJars = this.checkPaths(settings.viperServerSettings.serverJars, "viperServerPath:"); +// if (this.viperServerJars().trim().length == 0) { +// this.addError("Missing viper server jars at paths: " + JSON.Stringify(settings.viperServerSettings.serverJars)) +// } +// //check viperServerTimeout +// settings.viperServerSettings.timeout = this.checkTimeout(settings.viperServerSettings.timeout, "viperServerSettings:"); +// //check the customArguments +// } +// +// //no need to check preferences +// //check java settings +// if (!settings.javaSettings.customArguments) { +// this.addError("The customArguments are missing in the java settings"); +// } +// +// //checks done +// this._valid = !this._errors.some(error => error. +// type == +// SettingsErrorType.Error +// ); //if there is no error -> valid +// if (this._valid) { +// Log.log("The settings are ok", LogLevel.Info); +// resolve(true); +// } else { +// resolve(false); +// } +// } +// } +// } +// } +// +// for { +// errors <- getSettingErrorFuture +// versions <- getVersionsFuture(errors) +// result <- useVersions(versions) +// } yield result +// +// +// +// } +// +// }) +// } catch { +// Log.error("Error checking settings: " + e) +// resolve(false) +// } +// }) +// } + +// private def mergeBackend(custom: Backend, def: Backend) = { +// if (!custom || !def || custom.name != def.name) return; +// if (!custom.paths) custom.paths = def.paths; +// if (!custom.stages) custom.stages = def.stages +// else this.mergeStages(custom.stages, def.stages); +// if (!custom.timeout) custom.timeout = def.timeout; +// if (!custom.engine || custom.engine.length == 0) custom.engine = def.engine; +// if (!custom.type || custom.type.length == 0) custom.type = def.type; +// } +// +// private def mergeStages(custom: Stage[], defaultStages: Stage[]) = { +// defaultStages.forEach(def => { +// var cus = custom.filter(stage => stage.name == def.name)[0]; +// if (cus) { +// //merge +// if (cus.customArguments === undefined) cus.customArguments = def.customArguments; +// if (!cus.mainMethod) cus.mainMethod = def.mainMethod; +// if (cus.isVerification === undefined) cus.isVerification = def.isVerification; +// } else { +// custom.push(def); +// } +// }); +// } +// +// private def checkPlatformDependentUrl(url: String | PlatformDependentURL): String = { +// var StringURL = null; +// if (url) { +// if (typeof url === "String") { +// StringURL = url; +// } else { +// if (Settings.isLinux) { +// StringURL = url.linux; +// } else if (Settings.isMac) { +// StringURL = url.mac; +// } else if (Settings.isWin) { +// StringURL = url.windows; +// } else { +// Log.error("Operation System detection failed, Its not Mac, Windows or Linux"); +// } +// } +// } +// if (!StringURL || StringURL.length == 0) { +// this.addError("The viperToolsProvider is missing in the preferences"); +// } +// //TODO: check url format +// return StringURL; +// } + + private def checkPaths(paths: (String | Array[String] | PlatformDependentPath | PlatformDependentListOfPaths), prefix: String): Array[String] = { + //Log.log("checkPaths(" + JSON.Stringify(paths) + ")", LogLevel.LowLevelDebug); + var result: Array[String] = [] + var StringPaths: Array[String] = [] + if (!paths) { + this.addError(prefix + " paths are missing"); + } else if (typeof paths === "String") { + StringPaths.push(paths) + } else if (paths instanceof Array) { + paths.forEach(path => { + if (typeof path === "String") { + StringPaths.push(path) + } + }) + } else { + var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>paths; + if (Settings.isLinux) { + return this.checkPaths(platformDependentPath.linux, prefix); + } else if (Settings.isMac) { + return this.checkPaths(platformDependentPath.mac, prefix); + } else if (Settings.isWin) { + return this.checkPaths(platformDependentPath.windows, prefix); + } else { + Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); + } + return result; + } + + if (StringPaths.length == 0) { + this.addError(prefix + ' path has wrong type: expected: String | Array[String] | {windows:(String|Array[String]), mac:(String|Array[String]), linux:(String|Array[String])}, found: ' + typeof paths + " at path: " + JSON.Stringify(paths)); + } + + //resolve the paths + StringPaths = StringPaths.map(StringPath => { + var resolvedPath = Settings.resolvePath(StringPath, false); + if (!resolvedPath.exists) { + this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); + } + return resolvedPath.path + }); + if (StringPaths.length == 0) { + this.addError(prefix + ' no file found at at path: ' + JSON.Stringify(paths)); + } + //Log.log("checkPaths result: (" + JSON.Stringify(StringPaths) + ")", LogLevel.LowLevelDebug); + return StringPaths; + } + + private def checkPath(path: (String | PlatformDependentPath), prefix: String, executable: Boolean, allowPlatformDependentPath: Boolean, allowStringPath: Boolean = true, allowMissingPath = false): ResolvedPath = { + if (!path) { + if (!allowMissingPath) this.addError(prefix + " path is missing"); + return { path: null, exists: false }; + } + var StringPath: String; + if (typeof path === "String") { + if (!allowStringPath) { + this.addError(prefix + ' path has wrong type: expected: {windows:String, mac:String, linux:String}, found: ' + typeof path); + return { path: StringPath, exists: false }; + } + StringPath = [String>path; + } else { + if (!allowPlatformDependentPath) { + this.addError(prefix + ' path has wrong type: expected: String, found: ' + typeof path + " at path: " + JSON.Stringify(path)); + return { path: null, exists: false }; + } + var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>path; + if (Settings.isLinux) { + StringPath = platformDependentPath.linux; + } else if (Settings.isMac) { + StringPath = platformDependentPath.mac; + } else if (Settings.isWin) { + StringPath = platformDependentPath.windows; + } else { + Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); + } + } + + if (!StringPath || StringPath.length == 0) { + if (!allowMissingPath) { + this.addError(prefix + ' path has wrong type: expected: String' + (executable ? ' or {windows:String, mac:String, linux:String}' : "") + ', found: ' + typeof path + " at path: " + JSON.Stringify(path)); + } + return { path: StringPath, exists: false }; + } + var resolvedPath = Settings.resolvePath(StringPath, executable); + if (!resolvedPath.exists) { + this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); + } + return resolvedPath; + } + + private def checkBackends(backends: Backend[]) { + //Log.log("Checking backends...", LogLevel.Debug); + if (!backends || backends.length == 0) { + this.addError("No backend detected, specify at least one backend"); + return; + } + + var backendNames: Set[String> = new Set[String>(); + + for (var i = 0; i < backends.length; i++) { + var backend = backends[i]; + if (!backend) { + this.addError("Empty backend detected"); + } + else if (!backend.name || backend.name.length == 0) {//name there? + this.addWarning("Every backend setting should have a name."); + backend.name = "backend" + (i + 1); + } + var backendName = "Backend " + backend.name + ":"; + //check for duplicate backends + if (backendNames.has(backend.name)) this.addError("Dublicated backend name: " + backend.name); + backendNames.add(backend.name); + + //check stages + if (!backend.stages || backend.stages.length == 0) { + this.addError(backendName + " The backend setting needs at least one stage"); + continue; + } + + backend.engine = this.resolveEngine(backend.engine); + //check engine and type + if (this.useViperServer(backend) && !ViperServerService.isSupportedType(backend.type)) { + this.addError(backendName + "the backend type " + backend.type + " is not supported, try one of these: " + ViperServerService.supportedTypes); + } + + var stages: Set[String> = new Set[String>(); + var verifyStageFound = false; + for (var i = 0; i < backend.stages.length; i++) { + var stage: Stage = backend.stages[i]; + if (!stage) { + this.addError(backendName + " Empty stage detected"); + } + else if (!stage.name || stage.name.length == 0) { + this.addError(backendName + " Every stage needs a name."); + } else { + var backendAndStage = backendName + " Stage: " + stage.name + ":"; + //check for duplicated stage names + if (stages.has(stage.name)) + this.addError(backendName + " Duplicated stage name: " + stage.name); + stages.add(stage.name); + //check mainMethod + if (!stage.mainMethod || stage.mainMethod.length == 0) + this.addError(backendAndStage + " Missing mainMethod"); + //check customArguments + if (!stage.customArguments) { + this.addError(backendAndStage + " Missing customArguments"); + continue; + } + } + } + for (var i = 0; i < backend.stages.length; i++) { + var stage: Stage = backend.stages[i]; + var BackendMissingStage = backendName + ": Cannot find stage " + stage.name; + if (stage.onParsingError && stage.onParsingError.length > 0 && !stages.has(stage.onParsingError)) + this.addError(BackendMissingStage + "'s onParsingError stage " + stage.onParsingError); + if (stage.onTypeCheckingError && stage.onTypeCheckingError.length > 0 && !stages.has(stage.onTypeCheckingError)) + this.addError(BackendMissingStage + "'s onTypeCheckingError stage " + stage.onTypeCheckingError); + if (stage.onVerificationError && stage.onVerificationError.length > 0 && !stages.has(stage.onVerificationError)) + this.addError(BackendMissingStage + "'s onVerificationError stage " + stage.onVerificationError); + if (stage.onSuccess && stage.onSuccess.length > 0 && !stages.has(stage.onSuccess)) + this.addError(BackendMissingStage + "'s onSuccess stage " + stage.onSuccess); + } + + //check paths + if (!backend.paths || backend.paths.length == 0) { + if (!this.useViperServer(backend)) this.addError(backendName + " The backend setting needs at least one path"); + } else { + if (typeof backend.paths == 'String') { + var temp = backend.paths; + backend.paths = [temp]; + } + for (var i = 0; i < backend.paths.length; i++) { + //extract environment variable or leave unchanged + backend.paths[i] = Settings.checkPath(backend.paths[i], backendName, false, false).path; + } + } + + //check verification timeout + backend.timeout = this.checkTimeout(backend.timeout, "Backend " + backendName + ":"); + } + return null; + } + + private def checkTimeout(timeout: number, prefix: String): number { + if (!timeout || (timeout && timeout <= 0)) { + if (timeout && timeout < 0) { + this.addWarning(prefix + " The timeout of " + timeout + " is interpreted as no timeout."); + } + return null; + } + return timeout; + } + + def backendJars(backend: Backend): String { + var jarFiles = this.getAllJarsInPaths(backend.paths, false); + return this.buildDependencyString(jarFiles); + } + + def viperServerJars(): String { + var jarFiles = this.getAllJarsInPaths(Array[[String]]this.settings.viperServerSettings.serverJars, false); + return this.buildDependencyString(jarFiles); + } + + def buildDependencyString(jarFiles: Array[String]): String { + var dependencies = ""; + var concatenationSymbol = Settings.isWin ? ";" : ":"; + if (jarFiles.length > 0) { + dependencies = dependencies + concatenationSymbol + '"' + jarFiles.join('"' + concatenationSymbol + '"') + '"' + } + return dependencies; + } + + def getAllJarsInPaths(paths: Array[String], recursive: Boolean): Array[String] { + var result: Array[String] = []; + try { + paths.forEach(path => { + if (fs.lstatSync(path).isDirectory()) { + var files = fs.readdirSync(path); + var folders = [] + files.forEach(child => { + child = pathHelper.join(path, child) + if (!fs.lstatSync(child).isDirectory()) { + //child is a file + if (this.isJar(child)) { + //child is a jar file + result.push(child); + } + } else { + folders.push(child); + } + }) + if (recursive) { + result.push(...this.getAllJarsInPaths(folders, recursive)); + } + } else { + if (this.isJar(path)) { + result.push(path) + } + } + }) + } catch (e) { + Log.error("Error getting all Jars in Paths: " + e); + } + return result; + } + + private def isJar(file: String): Boolean { + return file ? file.trim().endsWith(".jar") : false; + } + + private def extractEnvVars(path: String): ResolvedPath { + if (path && path.length > 2) { + while (path.indexOf("%") >= 0) { + var start = path.indexOf("%") + var end = path.indexOf("%", start + 1); + if (end < 0) { + return { path: path, exists: false, error: "unbalanced % in path: " + path }; + } + var envName = path.subString(start + 1, end); + var envValue = process.env[envName]; + if (!envValue) { + return { path: path, exists: false, error: "environment variable " + envName + " used in path " + path + " is not set" }; + } + if (envValue.indexOf("%") >= 0) { + return { path: path, exists: false, error: "environment variable: " + envName + " must not contain %: " + envValue }; + } + path = path.subString(0, start - 1) + envValue + path.subString(end + 1, path.length); + } + } + return { path: path, exists: true }; + } + + private def resolvePath(path: String, executable: Boolean): ResolvedPath { + try { + if (!path) { + return { path: path, exists: false }; + } + path = path.trim(); + + //expand internal variables + var resolvedPath = this.expandViperToolsPath(path); + //handle env Vars + var envVarsExtracted = this.extractEnvVars(resolvedPath); + if (!envVarsExtracted.exists) return envVarsExtracted; + resolvedPath = envVarsExtracted.path; + + //handle files in Path env var + if (resolvedPath.indexOf("/") < 0 && resolvedPath.indexOf("\\") < 0) { + //its only a filename, try to find it in the path + var pathEnvVar: String = process.env.PATH; + if (pathEnvVar) { + var pathList: Array[String] = pathEnvVar.split(Settings.isWin ? ";" : ":"); + for (var i = 0; i < pathList.length; i++) { + var pathElement = pathList[i]; + var combinedPath = this.toAbsolute(pathHelper.join(pathElement, resolvedPath)); + var exists = this.exists(combinedPath, executable); + if (exists.exists) return exists; + } + } + } else { + //handle absolute and relative paths + if (this.home) { + resolvedPath = resolvedPath.replace(/^~($|\/|\\)/, `${this.home}$1`); + } + resolvedPath = this.toAbsolute(resolvedPath); + return this.exists(resolvedPath, executable); + } + return { path: resolvedPath, exists: false }; + } catch (e) { + Log.error("Error resolving path: " + e); + } + } + + private def exists(path: String, executable: Boolean): ResolvedPath { + try { + fs.accessSync(path); + return { path: path, exists: true }; + } catch (e) { } + if (executable && this.isWin && !path.toLowerCase().endsWith(".exe")) { + path += ".exe"; + //only one recursion at most, because the ending is checked + return this.exists(path, executable); + } + return { path: path, exists: false } + } + + private def toAbsolute(path: String): String { + return pathHelper.resolve(pathHelper.normalize(path)); + } + } + +class Version(versionNumbers: Array[Int] = Array(0, 0, 0)) { + private val _key = "VdafSZVOWpe"; + + var versionNumbers: Array[Int] = Array(0, 0, 0); + + def createFromVersion(version: Version): Version = { + try { + if (version != null) { + if (/\d+(\.\d+)+/.test(version)) { + return new Version(version.split(".").map(x => Number.parseInt(x))) + } + } + } catch { + case e: Throwable => Log.debug("Error creating version from Version: " + e); + } + + return new Version(); + } + + def createFromHash(hash) { + try { + if (hash) { + var version = this.decrypt(hash, _key); + //Log.log("hash: " + hash + " decrypted version: " + version, LogLevel.LowLevelDebug); + return this.createFromVersion(version); + } + } catch { + case e: Throwable => Log.debug("Error creating version from hash: " + e); + } + return new Version(); + } + + private def encrypt(msg: String, key: String): String { + var res: String = "" + var parity: number = 0; + for (var i = 0; i < msg.length; i++) { + var keyChar: number = key.charCodeAt(i % key.length); + //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); + var char: number = msg.charCodeAt(i); + //Log.log("char " + msg.charAt(i) + " charCode: " + char,LogLevel.LowLevelDebug); + var cypher: number = (char ^ keyChar) + parity = (parity + cypher % (16 * 16)) % (16 * 16); + //Log.log("cypher " + (char ^ keyChar).toString() + " hex: "+ cypher,LogLevel.LowLevelDebug); + res += this.pad(cypher); + } + return res + this.pad(parity); + } + + private def pad(n: number): String { + var s = n.toString(16); + return (s.length == 1 ? "0" : "") + s; + } + + private def decrypt(cypher: String, key: String): String { + //Log.log("decrypt",LogLevel.LowLevelDebug); + var res: String = "" + var parity: number = 0; + if (!cypher || cypher.length < 2 || cypher.length % 2 != 0) { + return ""; + } + for (var i = 0; i < cypher.length - 2; i += 2) { + var keyChar: number = key.charCodeAt((i / 2) % key.length); + //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); + var char: number = (16 * parseInt(cypher.charAt(i), 16)) + parseInt(cypher.charAt(i + 1), 16) + parity = (parity + char % (16 * 16)) % (16 * 16); + //Log.log("char " + char,LogLevel.LowLevelDebug); + //Log.log("encChar " + String.fromCharCode(char ^ keyChar) + " charCode: "+(char ^ keyChar),LogLevel.LowLevelDebug); + res += String.fromCharCode(char ^ keyChar) + } + if (parity != (16 * parseInt(cypher.charAt(cypher.length - 2), 16)) + parseInt(cypher.charAt(cypher.length - 1), 16)) { + return "" + } else { + return res + } + } + + toString(): String { + return this.versionNumbers.join("."); + } + + def testhash() { + var s = "1.0.0"; + var en = this.encrypt(s, Version.Key); + var de = this.decrypt(en, Version.Key); + Log.log("Hash Test: " + s + " -> " + en + " -> " + de, LogLevel.LowLevelDebug) + } + + def hash(version: String): String { + var hash = this.encrypt(version, Version.Key); + //Log.log("version: " + version + " hash: " + hash, LogLevel.LowLevelDebug); + return hash; + } + + //1: this is larger, -1 other is larger + compare(other: Version): number { + for (var i = 0; i < this.versionNumbers.length; i++) { + if (i >= other.versionNumbers.length) return 1; + if (this.versionNumbers[i] > other.versionNumbers[i]) return 1; + if (this.versionNumbers[i] < other.versionNumbers[i]) return -1; + } + return this.versionNumbers.length < other.versionNumbers.length ? -1 : 0; + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/VerificationTask.scala b/src/main/scala/viper/server/VerificationTask.scala new file mode 100644 index 0000000..e1c036e --- /dev/null +++ b/src/main/scala/viper/server/VerificationTask.scala @@ -0,0 +1,328 @@ +package viper.server + +import java.nio.file.Paths +import java.util.concurrent.{CompletableFuture => CFuture} + +import akka.actor.Actor +import akka.util.{Timeout => AkkaTimeout} +import org.eclipse.lsp4j.{Diagnostic, DiagnosticSeverity, Location, Position, PublishDiagnosticsParams, Range, SymbolInformation, SymbolKind} +import viper.server.VerificationState._ +import viper.server.VerificationSuccess._ +import viper.silver.ast.{Domain, Field, Method, Predicate, SourcePosition} +import viper.silver.reporter.{EntityFailureMessage, EntitySuccessMessage, OverallFailureMessage, OverallSuccessMessage, ProgramDefinitionsReport, ProgramOutlineReport, StatisticsReport} + +import scala.collection.JavaConverters._ +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.Future + +class VerificationTask(fileUri: String) { + //state that is valid across verifications + var verificationCount: Int = 0 + + // file under verification + var filename: String = _ + var path: String = _ + var lastSuccess: VerificationSuccess = NA + var internalErrorMessage: String = "" + + //state specific to one verification + var is_running: Boolean = false + var global_failure: Boolean = false + var is_aborting: Boolean = false + var state: VerificationState = Stopped + var manuallyTriggered: Boolean + + //verification results + var time: Long = 0 + var diagnostics: mutable.ArrayBuffer[Diagnostic] = _ +// var verifiables: Array[Verifiable] = _ + var parsingCompleted: Boolean = false + var typeCheckingCompleted: Boolean = false + var backendType: String = _ + var progress: Progress = _ +// var shownExecutionTrace: Array[ExecutionTrace] = _ + var symbolInformation: ArrayBuffer[SymbolInformation] = _ + var definitions: ArrayBuffer[Definition] = _ + var manuallyTriggered: Boolean = _ + + //working variables + private var lines: Array[String] = Array() + private var wrongFormat: Boolean = false + private var partialData: String = "" + + def resetDiagnostics() = { + diagnostics = ArrayBuffer() + val diagnosticParams = new PublishDiagnosticsParams() + diagnosticParams.setUri(fileUri) + diagnosticParams.setDiagnostics(diagnostics.asJava) + Coordinator.client.publishDiagnostics(diagnosticParams) + } + + def resetLastSuccess() = { + lastSuccess = NA + } + + def prepareVerification() = { + is_running = true + is_aborting = false + state = Stopped + lines = Array() + wrongFormat = false + if (partialData.length > 0) { + Log.debug("Some unparsed output was detected:\n" + partialData) + partialData = "" + } + time = 0 + resetDiagnostics() +// verifiables = Array() + parsingCompleted = true + typeCheckingCompleted = true + internalErrorMessage = "" + } + + def abortVerification(): CFuture[Unit] = { + if (!is_running) { + return CFuture.completedFuture() + } + Log.info("Aborting running verification.") + is_aborting = true + Coordinator.backendService.stopVerification().thenApply(_ => { + is_running = false + lastSuccess = Aborted + }).exceptionally(e => { + Log.debug("Error aborting verification of " + filename + ": " + e) + }) + } + + def verify(manuallyTriggered: Boolean): Boolean = { + prepareVerification() + this.manuallyTriggered = manuallyTriggered + + // This should have exactly one stage + val stage = Coordinator.backend.stages.head + if (stage == null) { + Log.debug("backend " + Coordinator.backend.name + " has no " + Settings.VERIFY + " stage, even though the settigns were checked.") + return false + } + + // why must this be variable? + path = Common.uriToPath(fileUri) + filename = Common.filenameFromPath(path) + Log.toLogFile("verify " + filename) + + verificationCount += 1 + Coordinator.executedStages.append(stage) + Log.info(Coordinator.backend.name + " verification started") + + val params = StateChangeParams(VerificationRunning, filename = filename) + Coordinator.sendStateChangeNotification(params, Some(this)) + + startVerificationTimeout(verificationCount) + Coordinator.backendService.startStageProcess(stage, path, stdOutHandler, stdErrHandler, completionHandler) + true + } + + class RelayActor() extends Actor { + + override def receive: PartialFunction[Any, Unit] = { + case ProgramOutlineReport(members) => + symbolInformation = ArrayBuffer() + members.foreach(m => { + val location: Location = new Location(fileUri, null) + val kind = m match { + case Method => SymbolKind.Method + case Function => SymbolKind.Function + case Predicate => SymbolKind.Interface + case Field => SymbolKind.Field + case Domain => SymbolKind.Class + case _ => SymbolKind.Enum + } + val info: SymbolInformation = new SymbolInformation(m.name, kind, location) + symbolInformation.append(info) + }) + case ProgramDefinitionsReport(defs) => + definitions = ArrayBuffer() + defs.foreach(d => { + val start = d.scope match { + case Some(s) => new Position(s.start.line, s.start.column) + case None => null + } + val end = d.scope match { + case Some(s) if s.end.isDefined => new Position(s.end.get.line, s.end.get.column) + case None => null + } + val range: Range = if(start != null && end != null) { + new Range(start, end) + } else { + null + } + val sourcePos = d.location.asInstanceOf[viper.silver.ast.SourcePosition] + val location: Position = new Position(sourcePos.start.line, sourcePos.start.column) + val definition: Definition = Definition(d.typ, d.name, location, range) + definitions.append(definition) + //Log.log("Definition: " + JSON.stringify(definition), LogLevel.LowLevelDebug) + }) + case StatisticsReport(m, f, p, _, _) => +// TODO: pass in task (probably as props to actor). + progress = new Progress(p, f, m) + val params = StateChangeParams(VerificationRunning, progress = 0, filename = filename) + Coordinator.sendStateChangeNotification(params, this) + case EntitySuccessMessage(_, concerning, _, _) => + if (progress == null) { + Log.debug("The backend must send a VerificationStart message before the ...Verified message.") + return + } + val output = BackendOutput(BackendOutputType.FunctionVerified, name = concerning.name) + progress.updateProgress(output) + val progressPercentage = progress.toPercent() + val params = StateChangeParams(VerificationRunning, progress = progressPercentage, filename = filename) + Coordinator.sendStateChangeNotification(params, this) + case EntityFailureMessage(ver, concerning, time, res, cached) => + res.errors.foreach(err => { + if (err.fullId != null && err.fullId == "typechecker.error") { + typeCheckingCompleted = false + } + else if (err.fullId != null && err.fullId == "parser.error") { + parsingCompleted = false + typeCheckingCompleted = false + } + val err_start = err.pos.asInstanceOf[SourcePosition].start + val err_end = err.pos.asInstanceOf[SourcePosition].end + val start_pos = new Position(err_start.line, err_start.column) + val end_pos = if(err_end.isDefined) { + new Position(err_end.get.line, err_end.get.column) + } else { + null + } + val range = new Range(start_pos, end_pos) + Log.toLogFile(s"Error: [${Coordinator.backend.name}] " + + s"${if(err.fullId != null) "[" + err.fullId + "] " else ""}" + + s"${range.getStart.getLine + 1}:${range.getStart.getCharacter + 1} ${err.readableMessage}s") + + + val cachFlag: String = if(err.cached) "(cached)" else "" + val diag = new Diagnostic(range, err.readableMessage + cachFlag, DiagnosticSeverity.Error, "") + diagnostics.append(diag) + + val params = StateChangeParams(VerificationRunning, filename = filename, nofErrors = diagnostics.length, uri = fileUri, diagnostics) + Coordinator.sendStateChangeNotification(params, this) + //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) + }) + case OverallSuccessMessage(ver, verificationTime) => + state = VerificationReporting + time = verificationTime + Coordinator.backendService.isSessionRunning = false + completionHandler(0) + case OverallFailureMessage(ver, verificationTime, failure) => + state = VerificationReporting + time = verificationTime + Coordinator.backendService.isSessionRunning = false + completionHandler(0) + case e: Throwable => + //receiving an error means the promise can be finalized with failure. + } + } + + private def determineSuccess(code: Int): VerificationSuccess = { + if (diagnostics.isEmpty && code == 0) { + VerificationSuccess.Success + } else if (diagnostics.nonEmpty) { + //use tag and backend trace as indicators for completed parsing + if (!parsingCompleted) { + ParsingFailed + } else if (parsingCompleted && !typeCheckingCompleted) { + TypecheckingFailed + } else { + VerificationFailed + } + } else if (is_aborting) { + Aborted + } else { + Error + } + } + + private def completionHandler(code: Int) { + try { + Log.debug("completionHandler is called with code ${code}") + if (is_aborting) { + is_running = false + return + } + var success = NA + + val isVerifyingStage = Coordinator.stage.getOrElse(return).isVerification + + //do we need to start a followUp Stage? + if (!is_aborting && isVerifyingStage) { + success = determineSuccess(code) + } + + if (!isVerifyingStage) { + success = Success + var params = StateChangeParams( + Ready, success = Success, manuallyTriggered = manuallyTriggered, + filename = filename, nofErrors = 0, time = time.toInt, + verificationCompleted = false, uri = fileUri, + error = internalErrorMessage) + Coordinator.sendStateChangeNotification(params, Some(this)) + } else { + if (partialData.length > 0) { + Log.debug("Some unparsed output was detected:\n" + partialData) + partialData = "" + } + + // Send the computed diagnostics to VSCode. + //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) + + //inform client about postProcessing + var params = StateChangeParams(PostProcessing, filename = filename) + Coordinator.sendStateChangeNotification(params, Some(this)) + + //notify client about outcome of verification + params = StateChangeParams(Ready, success = Success, manuallyTriggered = manuallyTriggered, + filename = filename, nofErrors = diagnostics.length, time = time.toInt, + verificationCompleted = true, uri = fileUri, error = internalErrorMessage) + Coordinator.sendStateChangeNotification(params, Some(this)) + + if (code != 0 && code != 1 && code != 899) { + Log.debug("Verification Backend Terminated Abnormaly: with code " + code) + } + } + + //reset for next verification + lastSuccess = success + time = 0 + is_running = false + } catch { + case e: Throwable => + is_running = false + Coordinator.client.notifyVerificationNotStarted(fileUri) + Log.debug("Error handling verification completion: ", e) + } + } + + private def startVerificationTimeout(verificationCount: Int) = { +// if (Coordinator.backend.timeout > 0) { +// Log.lowLevel("Set verification timeout to " + Coordinator.backend.timeout) +// +// def timeout_callback(): Unit = { //aborts the verification on time out +// if (is_running && this.verificationCount == verificationCount) { +// Log.hint("The verification timed out after " + Coordinator.backend.timeout + "ms") +// abortVerification().thenAccept(() => { +// val params = StateChangeParams(Ready, verificationCompleted = false, +// success = Timeout, verificationNeeded = false, +// uri = fileUri) +// Coordinator.sendStateChangeNotification(params, Some(this)) +// }) +// } +// is_running = false +// } +// //TODO find timeout mechanism +// setTimeout(timeout_callback(), Coordinator.backend.timeout) +// } else { +// Log.lowLevel("No verification timeout set") +// } + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/ViperLanguageServer.scala b/src/main/scala/viper/server/ViperLanguageServer.scala deleted file mode 100644 index cd412cf..0000000 --- a/src/main/scala/viper/server/ViperLanguageServer.scala +++ /dev/null @@ -1,93 +0,0 @@ -package viper.server - -import java.util.concurrent.CompletableFuture -import scala.collection.JavaConverters._ - -import com.google.gson.JsonPrimitive -import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} -import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionOptions, CompletionParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, MessageParams, MessageType, ServerCapabilities, TextDocumentSyncKind} -import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware, LanguageServer, TextDocumentService, WorkspaceService} - -class ViperLanguageServer extends LanguageClientAware { - private var _client: Option[LanguageClient] = None - - //==========================================================================// - // ADD MORE METHODS TO UNDERSTAND CLIENT HERE // - //==========================================================================// - - @JsonNotification("sbi/hi") - def saidHi(params: DidSaveTextDocumentParams): Unit = { - println("said hi") - _client match { - case Some(c) => c.showMessage(new MessageParams(MessageType.Info, "Hi back <3")) - case _ => - } - } - - @JsonNotification("textDocument/didChange") - def didChange(params: DidChangeTextDocumentParams): Unit = { - println("didChange") - _client match { - case Some(c) => - c.showMessage(new MessageParams(MessageType.Log, s"Huuuhuuuuu")) - case _ => - } - } - - @JsonRequest(value = "initialize") - def initialize(params: InitializeParams): CompletableFuture[InitializeResult] = { - println("initialize") - val capabilities = new ServerCapabilities() - // always send full text document for each notification: - capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) - capabilities.setCompletionProvider(new CompletionOptions(true, null)) - CompletableFuture.completedFuture(new InitializeResult(capabilities)) - } - - @JsonRequest(value = "shutdown") - def shutdown(): CompletableFuture[AnyRef] = { - println("shutdown") - CompletableFuture.completedFuture(null) - } - - @JsonNotification(value = "exit") - def exit(): Unit = { - println("exit") - sys.exit() - } - - - @JsonRequest("textDocument/completion") - def completion(params: CompletionParams): CompletableFuture[CompletionList] = { - val tsItem = new CompletionItem("TypeScript") - tsItem.setKind(CompletionItemKind.Text) - tsItem.setData(1) - val jsItem = new CompletionItem("JavaScript") - jsItem.setKind(CompletionItemKind.Text) - jsItem.setData(2) - val completions = new CompletionList(List(tsItem, jsItem).asJava) - CompletableFuture.completedFuture(completions) - } - - @JsonRequest("completionItem/resolve") - def completionItemResolve(item: CompletionItem): CompletableFuture[CompletionItem] = { - val data: Object = item.getData - data match { - case n: JsonPrimitive if n.getAsInt == 1 => { - item.setDetail("TypeScript details") - item.setDocumentation("TypeScript documentation") - } - case n: JsonPrimitive if n.getAsInt == 2 => { - item.setDetail("JavaScript details") - item.setDocumentation("JavaScript documentation") - } - case _ => item.setDetail(s"${data.toString} is instance of ${data.getClass}") - } - CompletableFuture.completedFuture(item) - } - - override def connect(client: LanguageClient): Unit = { - println("Connecting plugin client.") - _client = Some(client) - } -} diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index ed201ed..1e7a944 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -10,15 +10,14 @@ object ViperServerRunner { def main(args: Array[String]): Unit = { try { - var port = Integer.parseInt(args.head) + val port = Integer.parseInt(args.head) runServer(port) } catch { - case e: NoSuchElementException => { + case _: NoSuchElementException => { println("No port number provided") sys.exit(1) - return } - case e: NumberFormatException => { + case _: NumberFormatException => { println("Invalid port number") sys.exit(1) return @@ -30,11 +29,17 @@ object ViperServerRunner { // start listening on port try { val socket = new Socket("localhost", port) - println(s"going to listen on port $port") + val localAddress = socket.getLocalAddress.getHostAddress + println(s"going to listen on $localAddress:$port") + + Coordinator.port = port + Coordinator.url = localAddress - val server: ViperLanguageServer = new ViperLanguageServer() + + val server: LanguageServerReceiver = new LanguageServerReceiver() val launcher = Launcher.createLauncher(server, classOf[LanguageClient], socket.getInputStream, socket.getOutputStream) server.connect(launcher.getRemoteProxy) + // start listening on input stream in a new thread: val fut = launcher.startListening() // wait until stream is closed again diff --git a/src/main/scala/viper/server/ViperServerService.scala b/src/main/scala/viper/server/ViperServerService.scala new file mode 100644 index 0000000..22dec7e --- /dev/null +++ b/src/main/scala/viper/server/ViperServerService.scala @@ -0,0 +1,273 @@ +package viper.server + +import java.util.concurrent.{CompletableFuture => CFuture} + +import akka.actor.{Actor, PoisonPill, Props} + +import scala.compat.java8.FutureConverters._ +import akka.pattern.ask +import akka.util.Timeout + +import scala.concurrent.Future +import scala.concurrent.duration._ +import viper.server.core.{VerificationJobHandler, ViperCoreServer} +import viper.server.protocol.ViperServerProtocol.Stop +import viper.server.VerificationState._ +import viper.server.VerificationSuccess._ +import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} +import viper.server.utility.AstGenerator +import viper.silver.ast.{Method, Program} +import viper.silver.reporter.{Message, ProgramOutlineReport} + +class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { + var instanceCount: Int = 0 + var isSessionRunning: Boolean = false + + private var _ready: Boolean = false + + protected var timeout: Int = _ + + private var _server_logfile: String = _ + + // the JID that ViperServer assigned to the current verification job. + private var _job_id: Int = _ + + def isReady: Boolean = _ready; + + def setReady(backend: Backend): Unit = { + _ready = true + Coordinator.backend = backend + Coordinator.startingOrRestarting = false + Log.info("The backend is ready for verification") + Coordinator.client.notifyBackendReady(S2C_Commands.BackendReady) + } + + def startStageProcess( + stage: Stage, fileToVerify: String, + onData: String => Unit, + onError: String => Unit, + onClose: Int => Unit): Unit = { + + try { + Log.lowLevel("Start Stage Process") + + isSessionRunning = true + val command = getStageCommand(fileToVerify, stage); + startVerifyStream(command) + } catch { + case e: Throwable => Log.debug("Error starting stage process: " + e); + } + } + + def getStageCommand(fileToVerify: String, stage: Stage): String = { + val args: String = getViperBackendClassName(stage) + " " + stage.customArguments + val command = Settings.expandCustomArguments(args, stage, fileToVerify, Coordinator.backend) + Log.debug(command) + command + } + + def getViperBackendClassName(stage: Stage): String = { + Coordinator.backend.backend_type match { + case "silicon" => "silicon" + case "carbon" => "carbon" + case "other" => stage.mainMethod + case _ => throw new Error(s"Invalid verification backend value. " + + s"Possible values are [silicon | carbon | other] " + + s"but found ${Coordinator.backend}") + } + } + + def swapBackend(newBackend: Backend): Unit = { + setReady(newBackend) + } + + def stopVerification(): CFuture[Boolean] = { + lookupJob(_job_id) match { + case Some(handle_future) => + handle_future.flatMap(handle => { + implicit val askTimeout: Timeout = Timeout(config.actorCommunicationTimeout() milliseconds) + val interrupt: Future[String] = (handle.controller_actor ? Stop(true)).mapTo[String] + handle.controller_actor ! PoisonPill // the actor played its part. + interrupt + }).toJava.toCompletableFuture.thenApply(msg => { + Log.info(msg); + true + }) + case _ => + // Did not find a job with this jid. + CFuture.failedFuture(new Throwable(s"The verification job #$_job_id does not exist.")) + } + } + + def setStopping(): Unit = { + Log.debug("Set Stopping... ") + _ready = false + Coordinator.startingOrRestarting = false + Coordinator.sendStateChangeNotification(StateChangeParams(Stopping), None) + } + + def setStopped(): Unit = { + Log.debug("Set Stopped. ") + _ready = false + Coordinator.startingOrRestarting = false + Coordinator.sendStateChangeNotification(StateChangeParams(Stopped), None) + } + + private def getArgListFromArgString(arg_str: String): List[String] = { + val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r + val quoted_string = """^["'](.*)["']$""".r + possibly_quoted_string.findAllIn(arg_str).toList.map { + case quoted_string(noqt_a) => noqt_a + case a => a + } + } + + def startVerify(command: String): Int = { + Log.debug("Requesting ViperServer to start new job...") + // Todo start verification in VCS + + val arg_list = getArgListFromArgString(command) + val file: String = arg_list.last + val arg_list_partial = arg_list.dropRight(1) + + // Parse file + + val astGen = new AstGenerator(logger) + var ast_option: Option[Program] = None + try { + ast_option = astGen.generateViperAst(file) + } catch { + case _: java.nio.file.NoSuchFileException => + Log.debug("The file for which verification has been requested was not found.") + return + } + val ast = ast_option.getOrElse(return Log.debug("The file for which verification has been requested contained syntax errors.")) + + // prepare backend config + val backend = arg_list_partial match { + case "silicon" :: args => SiliconConfig(args) + case "carbon" :: args => CarbonConfig(args) + case "custom" :: args => CustomConfig(args) + } + + val jid: VerificationJobHandler = verify(file, backend, ast) + + if (jid.id >= 0) { + logger.get.info(s"Verification process #${jid.id} has successfully started.") + } else { + logger.get.error(s"Could not start verification process. " + + s"The maximum number of active verification jobs are currently running (${MAX_ACTIVE_JOBS}).") + Log.debug(s"the maximum number of active verification jobs are currently running (${MAX_ACTIVE_JOBS}).") + } + jid.id + } + + // This method should start a verification and, after getting back the corresponding jobId, should + // immmediately ask for messages. + private def startVerifyStream(command: String): Unit = { + Log.debug("Sending verification request to ViperServer...") + _job_id = startVerify(command) + val relay_actor = system.actorOf(RelayActor.props()) + val termination_status = streamMessages(_job_id, relay_actor) + } + + private object RelayActor { + case object Result + def props(): Props = Props(new RelayActor()) + } + + def flushCache(filePath?: String): CFuture[String] = { + return new Promise((resolve, reject) => { + val url = this._url + ':' + this._port + '/cache/flush' + if (filePath) { + Log.log(`Requesting ViperServer to flush the cache for (` + filePath + `)...`, LogLevel.Info) + + val options = { + url: url, + headers: {'content-type': 'application/json'}, + body: JSON.stringify({ backend: Coordinator.backend.name, file: filePath }) + } + + request.post(options).on('error', (error) => { + Log.log(`error while requesting ViperServer to flush the cache for (` + filePath + `).` + + ` Request URL: ${url}\n` + + ` Error message: ${error}`, LogLevel.Default) + reject(error) + + }).on('data', (data) => { + val response = JSON.parse(data.toString()) + if ( !response.msg ) { + Log.log(`ViperServer did not complain about the way we requested it to flush the cache for (` + filePath + `).` + + ` However, it also did not provide the expected bye-bye message.` + + ` It said: ${data.toString}`, LogLevel.Debug) + resolve(response) + } else { + Log.log(`ViperServer has confirmed that the cache for (` + filePath + `) has been flushed.`, LogLevel.Debug) + resolve(response.msg) + } + }) + + } else { + Log.log(`Requesting ViperServer to flush the entire cache...`, LogLevel.Info) + + request.get(url).on('error', (error) => { + Log.log(`error while requesting ViperServer to flush the entire cache.` + + ` Request URL: ${url}\n` + + ` Error message: ${error}`, LogLevel.Default) + reject(error) + + }).on('data', (data) => { + val response = JSON.parse(data.toString()) + if ( !response.msg ) { + Log.log(`ViperServer did not complain about the way we requested it to flush the entire cache.` + + ` However, it also did not provide the expected bye-bye message.` + + ` It said: ${data.toString}`, LogLevel.Debug) + resolve(response) + } else { + Log.log(`ViperServer has confirmed that the entire cache has been flushed.`, LogLevel.Debug) + resolve(response.msg) + } + }) + } + }) + } + + private def sendStopRequest(): CFuture[Boolean] = { + return new Promise((resolve, reject) => { + Log.log(`Requesting ViperServer to exit...`, LogLevel.Debug) + val url = this._url + ':' + this._port + '/exit' + request.get(url).on('error', (err) => { + Log.log(`error while requesting ViperServer to stop.` + + ` Request URL: ${url}\n` + + ` Error message: ${err}`, LogLevel.Default) + reject(err) + }).on('data', (data) => { + val response = JSON.parse(data.toString()) + if ( !response.msg ) { + Log.log(`ViperServer did not complain about the way we requested it to exit.` + + ` However, it also did not provide the expected bye-bye message.` + + ` It said: ${data.toString}`, LogLevel.Debug) + resolve(true) + } else if ( response.msg !== 'shutting down...' ) { + Log.log(`ViperServer responded with an unexpected bye-bye message: ${response.msg}`, + LogLevel.Debug) + resolve(true) + } else { + Log.log(`ViperServer has exited properly.`, LogLevel.Debug) + resolve(true) + } + }) + }) + } + + def isSupportedType(t: String) = { + if (!t) { + return false + } + return t.toLowerCase() == 'carbon' || t.toLowerCase() == 'silicon' || t.toLowerCase() == 'other' + } + + def supportedTypes(): String = { + "'carbon', 'silicon', 'other'" + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index 3e73af8..c0f4291 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -238,7 +238,6 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program }).mapValues(_.size) private def reportProgramStats(prog: Program): Unit = { val stats = countInstances(prog) - _frontend.reporter.report(ProgramOutlineReport(prog.members.toList)) _frontend.reporter.report(StatisticsReport( stats.getOrElse("method", 0), diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 05e45ab..7db57eb 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -44,7 +44,7 @@ class ViperCoreServer(private val _args: Array[String]) { private var _config: ViperConfig = _ final def config: ViperConfig = _config - private var _logger: ViperLogger = _ + protected var _logger: ViperLogger = _ final def logger: ViperLogger = _logger implicit val materializer: ActorMaterializer = ActorMaterializer() diff --git a/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala b/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala index 23503f9..d0bd395 100644 --- a/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala @@ -50,19 +50,19 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs implicit val position_writer: RootJsonFormat[Position] = lift(new RootJsonWriter[Position] { override def write(obj: Position): JsValue = JsObject( - "file" -> (if (obj.file != null) { - //FIXME this hack is needed due to the following bug in Silver: https://bitbucket.org/viperproject/silver/issues/232 - obj.file.toJson - } else { - JsString("") - }), - "start" -> JsString(s"${obj.line}:${obj.column}"), - "end" -> (obj.end match { - case Some(end_pos) => - JsString(s"${end_pos.line}:${end_pos.column}") - case _ => - JsString(s"") - })) + "file" -> (if (obj.file != null) { + //FIXME this hack is needed due to the following bug in Silver: https://bitbucket.org/viperproject/silver/issues/232 + obj.file.toJson + } else { + JsString("") + }), + "start" -> JsString(s"${obj.line}:${obj.column}"), + "end" -> (obj.end match { + case Some(end_pos) => + JsString(s"${end_pos.line}:${end_pos.column}") + case _ => + JsString(s"") + })) }) implicit val optionAny_writer: RootJsonFormat[Option[Any]] = lift(new RootJsonWriter[Option[Any]] { From df0e5696bb22af0c71fb0df23ba2414b8ed76f31 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 9 Oct 2020 18:11:51 +0200 Subject: [PATCH 03/79] compilable version --- .../scala/viper/server/CommandProtocol.scala | 66 +- src/main/scala/viper/server/Common.scala | 369 +++-- src/main/scala/viper/server/Coordinator.scala | 45 +- .../scala/viper/server/DataProtocol.scala | 49 +- ...rificationTask.scala => FileManager.scala} | 157 +- .../viper/server/IdeLanguageClient.scala | 8 +- src/main/scala/viper/server/IdeLog.scala | 4 +- .../viper/server/LanguageServerReceiver.scala | 85 +- src/main/scala/viper/server/Progress.scala | 7 +- src/main/scala/viper/server/Settings.scala | 1332 +++++++++-------- .../scala/viper/server/ViperHttpServer.scala | 23 +- .../viper/server/ViperServerService.scala | 273 ++-- .../viper/server/core/ViperCoreServer.scala | 4 +- .../server/protocol/ViperIDEProtocol.scala | 1 - .../server/writer/AlloySolutionWriter.scala | 2 +- 15 files changed, 1187 insertions(+), 1238 deletions(-) rename src/main/scala/viper/server/{VerificationTask.scala => FileManager.scala} (68%) diff --git a/src/main/scala/viper/server/CommandProtocol.scala b/src/main/scala/viper/server/CommandProtocol.scala index e505e7e..49f217e 100644 --- a/src/main/scala/viper/server/CommandProtocol.scala +++ b/src/main/scala/viper/server/CommandProtocol.scala @@ -1,40 +1,40 @@ package viper.server object C2S_Commands { - val RequestBackendNames = "RequestBackendNames" //void - val Dispose = "Dispose" //void - val Verify = "Verify" //VerifyParams - val StopVerification = "StopVerification"//filePath:String - val ShowHeap = "ShowHeap"//ShowHeapParams - val StartBackend = "StartBackend"//backendName:String - val StopBackend = "StopBackend"//void - val SwapBackend = "SwapBackend"//backendName:String - val GetExecutionTrace = "GetExecutionTrace"//GetExecutionTraceParams -> trace:ExecutionTrace[] - val RemoveDiagnostics = "RemoveDiagnostics" - val UpdateViperTools = "UpdateViperTools" - val GetViperFileEndings = "GetViperFileEndings" - val ViperUpdateComplete = "ViperUpdateComplete" - val FlushCache = "FlushCache" - val GetIdentifier = "GetIdentifier" + final val RequestBackendNames = "RequestBackendNames" //void + final val Dispose = "Dispose" //void + final val Verify = "Verify" //VerifyParams + final val StopVerification = "StopVerification"//filePath:String + final val ShowHeap = "ShowHeap"//ShowHeapParams + final val StartBackend = "StartBackend"//backendName:String + final val StopBackend = "StopBackend"//void + final val SwapBackend = "SwapBackend"//backendName:String + final val GetExecutionTrace = "GetExecutionTrace"//GetExecutionTraceParams -> trace:ExecutionTrace[] + final val RemoveDiagnostics = "RemoveDiagnostics" + final val UpdateViperTools = "UpdateViperTools" + final val GetViperFileEndings = "GetViperFileEndings" + final val ViperUpdateComplete = "ViperUpdateComplete" + final val FlushCache = "FlushCache" + final val GetIdentifier = "GetIdentifier" } object S2C_Commands { - val BackendChange = "BackendChange" - val CheckIfSettingsVersionsSpecified = "CheckIfSettingsVersionsSpecified" - val SettingsChecked = "SettingsChecked" //SettingsCheckedParams - val RequestRequiredVersion = "RequestRequiredVersion" //void -> requiredVersions: Versions - val StateChange = "StateChange" //StateChangeParams - val Log = "Log" //LogParams - val Error = "Error" //LogParams - val ToLogFile = "ToLogFile" //LogParams - val Hint = "Hint" //message: String - val Progress = "Progress" //message: {domain:String, curr:number, total:number} - val FileOpened = "FileOpened" //uri: String - val FileClosed = "FileClosed" //uri: String - val VerificationNotStarted = "VerificationNotStarted" //uri: String - val StopDebugging = "StopDebugging" //void - val BackendReady = "BackendReady" //BackendReadyParams - val StepsAsDecorationOptions = "StepsAsDecorationOptions" //StepsAsDecorationOptionsResult - val HeapGraph = "HeapGraph" //HeapGraph - val UnhandledViperServerMessageType = "UnhandledViperServerMessageType" + final val BackendChange = "BackendChange" + final val CheckIfSettingsVersionsSpecified = "CheckIfSettingsVersionsSpecified" + final val SettingsChecked = "SettingsChecked" //SettingsCheckedParams + final val RequestRequiredVersion = "RequestRequiredVersion" //void -> requiredVersions: Versions + final val StateChange = "StateChange" //StateChangeParams + final val Log = "Log" //LogParams + final val Error = "Error" //LogParams + final val ToLogFile = "ToLogFile" //LogParams + final val Hint = "Hint" //message: String + final val Progress = "Progress" //message: {domain:String, curr:number, total:number} + final val FileOpened = "FileOpened" //uri: String + final val FileClosed = "FileClosed" //uri: String + final val VerificationNotStarted = "VerificationNotStarted" //uri: String + final val StopDebugging = "StopDebugging" //void + final val BackendReady = "BackendReady" //BackendReadyParams + final val StepsAsDecorationOptions = "StepsAsDecorationOptions" //StepsAsDecorationOptionsResult + final val HeapGraph = "HeapGraph" //HeapGraph + final val UnhandledViperServerMessageType = "UnhandledViperServerMessageType" } \ No newline at end of file diff --git a/src/main/scala/viper/server/Common.scala b/src/main/scala/viper/server/Common.scala index a693661..6c84890 100644 --- a/src/main/scala/viper/server/Common.scala +++ b/src/main/scala/viper/server/Common.scala @@ -3,7 +3,7 @@ package viper.server import java.nio.file.Paths import java.util.concurrent.CompletableFuture -import org.eclipse.lsp4j.{Position} +import org.eclipse.lsp4j.Position object Common { var viperFileEndings: Array[String] = _ @@ -21,11 +21,15 @@ object Common { "TODO" } - def refreshEndings(): CompletableFuture[Unit] = { - Coordinator.client.requestVprFileEndings().thenApply(endings => { + def refreshEndings(): CompletableFuture[Void] = { + def f(endings: Array[String]): Unit = { viperFileEndings = endings + } + Coordinator.client.requestVprFileEndings().thenAccept((s:Array[String]) => { + viperFileEndings = s }).exceptionally(e => { Log.debug(s"GetViperFileEndings request was rejected by the client: $e") + null }) } @@ -36,7 +40,7 @@ object Common { CompletableFuture.completedFuture(endingMatches) } else { // need to refresh endings and then compare Log.debug("Refreshing the viper file endings.") - refreshEndings().thenApply(_ => { + refreshEndings().thenApply(a => { if (areEndingsDefined) { viperFileEndings.exists(ending => uri.endsWith(ending)) } else { @@ -46,31 +50,31 @@ object Common { } } - def prettyRange(range: Range): String = { - s"${prettyPos(range.start)}-${prettyPos(range.end)}" - } +// def prettyRange(range: Range): String = { +// s"${prettyPos(range.start)}-${prettyPos(range.end)}" +// } - def prettyPos(pos: Position): String = { - s"${pos.line + 1}:${pos.character + 1}" - } +// def prettyPos(pos: Position): String = { +// s"${pos.line + 1}:${pos.character + 1}" +// } - def getPositionOfState(index): Position = { - if (index >= 0 && index < this.steps.length) { - if (this.steps[index].position) { - return this.steps[index].position - } else { - return { - line: 0 - , character: 0 - } - } - } else { - return { - line: -1 - , character: -1 - } - } - } +// def getPositionOfState(index): Position = { +// if (index >= 0 && index < this.steps.length) { +// if (this.steps[index].position) { +// return this.steps[index].position +// } else { +// return { +// line: 0 +// , character: 0 +// } +// } +// } else { +// return { +// line: -1 +// , character: -1 +// } +// } +// } def comparePosition(a: Position, b: Position): Int = { if (a == null && b == null) return 0 @@ -85,69 +89,69 @@ object Common { } } - private def comparePositionAndIndex(a: Statement, b: Statement): Int = { - if (!a && !b) return 0 - if (!a) return -1 - if (!b) return 1 - if (a.position.line < b.position.line || (a.position.line === b.position.line && a.position.character < b.position.character)) { - return -1 - } else if (a.position.line === b.position.line && a.position.character === b.position.character) { - return (a.index < b.index) ? -1: 1 - } else { - return 1 - } - } - - private def compareByIndex(a: Statement, b: Statement): Int = { - if (!a && !b) return 0 - if (!a) return -1 - if (!b) return 1 - if (a.index < b.index) { - return -1 - } else if (a.index === b.index) { - return 0 - } else { - return 1 - } - } - - private def prettySteps(steps: Array[Statement]): String = { - try { - val res: String = "" - val methodIndex = - 1 - val currentMethodOffset = - 1 - val maxLine = 0 - val indent = "" - val allBordersPrinted = false - - // val currentMethod - - val numberOfClientSteps = 0 - steps.foreach((element, i) => { - val clientNumber = element.decorationOptions ? "" + element.decorationOptions.numberToDisplay: "" - - if (element.canBeShownAsDecoration) { - numberOfClientSteps ++ - } - - val parent = element.getClientParent () - if (parent && element.decorationOptions) { - clientNumber += " " + parent.decorationOptions.numberToDisplay - } +// private def comparePositionAndIndex(a: Statement, b: Statement): Int = { +// if (!a && !b) return 0 +// if (!a) return -1 +// if (!b) return 1 +// if (a.position.line < b.position.line || (a.position.line === b.position.line && a.position.character < b.position.character)) { +// return -1 +// } else if (a.position.line === b.position.line && a.position.character === b.position.character) { +// return (a.index < b.index) ? -1: 1 +// } else { +// return 1 +// } +// } - val serverNumber = "" + i - val spacesToPut = 8 - clientNumber.length - serverNumber.length - spacesToPut = spacesToPut < 0 ? 0: spacesToPut - res += `\n\t${clientNumber} ${"\t".repeat(spacesToPut)}(${serverNumber})|${"\t".repeat(element.depthLevel())} ${element.firstLine()}` - }) +// private def compareByIndex(a: Statement, b: Statement): Int = { +// if (!a && !b) return 0 +// if (!a) return -1 +// if (!b) return 1 +// if (a.index < b.index) { +// return -1 +// } else if (a.index === b.index) { +// return 0 +// } else { +// return 1 +// } +// } - res += '\ nNumberOfClientSteps: ' + numberOfClientSteps - //Log.log("Steps:\n" + res, LogLevel.LowLevelDebug) - return res - } catch (e) { - Log.debug ("Runtime Error in Pretty Steps: " + e) - } - } +// private def prettySteps(steps: Array[Statement]): String = { +// try { +// val res: String = "" +// val methodIndex = - 1 +// val currentMethodOffset = - 1 +// val maxLine = 0 +// val indent = "" +// val allBordersPrinted = false +// +// // val currentMethod +// +// val numberOfClientSteps = 0 +// steps.foreach((element, i) => { +// val clientNumber = element.decorationOptions ? "" + element.decorationOptions.numberToDisplay: "" +// +// if (element.canBeShownAsDecoration) { +// numberOfClientSteps ++ +// } +// +// val parent = element.getClientParent () +// if (parent && element.decorationOptions) { +// clientNumber += " " + parent.decorationOptions.numberToDisplay +// } +// +// val serverNumber = "" + i +// val spacesToPut = 8 - clientNumber.length - serverNumber.length +// spacesToPut = spacesToPut < 0 ? 0: spacesToPut +// res += `\n\t${clientNumber} ${"\t".repeat(spacesToPut)}(${serverNumber})|${"\t".repeat(element.depthLevel())} ${element.firstLine()}` +// }) +// +// res += '\ nNumberOfClientSteps: ' + numberOfClientSteps +// //Log.log("Steps:\n" + res, LogLevel.LowLevelDebug) +// return res +// } catch (e) { +// Log.debug ("Runtime Error in Pretty Steps: " + e) +// } +// } //Helper methods for child processes // def executer(command: String, dataHandler?: (String) => void, errorHandler?: (String) => void, exitHandler?: () => void): child_process.ChildProcess = { @@ -221,103 +225,98 @@ object Common { // return true // } - def makeSureFileExistsAndCheckForWritePermission(filePath: String, firstTry = true): Future[any] = { - return new Future((resolve, reject) => { - try { - val folder = pathHelper.dirname(filePath) - mkdirp(folder, (err) => { - if (err && err.code != 'EEXIST') { - resolve(err.code + ": Error creating " + folder + " " + err.message) - } else { - fs.open(filePath, 'a', (err, file) => { - if (err) { - resolve(err.code + ": Error opening " + filePath + " " + err.message) - } else { - fs.close(file, err => { - if (err) { - resolve(err.code + ": Error closing " + filePath + " " + err.message) - } else { - fs.access(filePath, 2, (e) => { //fs.constants.W_OK is 2 - if (e) { - resolve(e.code + ": Error accessing " + filePath + " " + e.message) - } else { - resolve(null) - } - }) - } - }) - } - }) - } - }) - } catch (e) { - resolve(e) - } - }) - } - - def extract(filePath: String): Future[Boolean] = { - return new Future((resolve, reject) => { - try { - //extract files - Log.log("Extracting files...", LogLevel.Info) - Log.startProgress() - val unzipper = new DecompressZip(filePath) - - unzipper.on('progress', function (fileIndex, fileCount) { - Log.progress("Extracting Viper Tools", fileIndex + 1, fileCount, LogLevel.Debug) - }) - - unzipper.on('error', function (e) { - if (e.code && e.code == 'ENOENT') { - Log.debug("Error updating the Viper Tools, missing create file permission in the viper tools directory: " + e) - } else if (e.code && e.code == 'EACCES') { - Log.debug("Error extracting " + filePath + ": " + e + " | " + e.message) - } else { - Log.debug("Error extracting " + filePath + ": " + e) - } - resolve(false) - }) - - unzipper.on('extract', function (log) { - resolve(true) - }) - - unzipper.extract({ - path: pathHelper.dirname(filePath), - filter: function (file) { - return file.type !== "SymbolicLink" - } - }) - } catch (e) { - Log.debug("Error extracting viper tools: " + e) - resolve(false) - } - }) - } - - def getParentDir(fileOrFolderPath: String): String = { - if (!fileOrFolderPath) return null - val obj = pathHelper.parse(fileOrFolderPath) - if (obj.base) { - return obj.dir - } - val folderPath = obj.dir - val is_matching = folderPath.match(/(^.*)[\/\\].+$/) //the regex retrieves the parent directory - if (is_matching) { - if (is_matching[1] == fileOrFolderPath) { - Log.debug("getParentDir has a fixpoint at " + fileOrFolderPath) - return null - } - return match[1] - } - else { - return null - } - } - - - +// def makeSureFileExistsAndCheckForWritePermission(filePath: String, firstTry = true): Future[any] = { +// return new Future((resolve, reject) => { +// try { +// val folder = pathHelper.dirname(filePath) +// mkdirp(folder, (err) => { +// if (err && err.code != 'EEXIST') { +// resolve(err.code + ": Error creating " + folder + " " + err.message) +// } else { +// fs.open(filePath, 'a', (err, file) => { +// if (err) { +// resolve(err.code + ": Error opening " + filePath + " " + err.message) +// } else { +// fs.close(file, err => { +// if (err) { +// resolve(err.code + ": Error closing " + filePath + " " + err.message) +// } else { +// fs.access(filePath, 2, (e) => { //fs.constants.W_OK is 2 +// if (e) { +// resolve(e.code + ": Error accessing " + filePath + " " + e.message) +// } else { +// resolve(null) +// } +// }) +// } +// }) +// } +// }) +// } +// }) +// } catch (e) { +// resolve(e) +// } +// }) +// } +// def extract(filePath: String): Future[Boolean] = { +// return new Future((resolve, reject) => { +// try { +// //extract files +// Log.log("Extracting files...", LogLevel.Info) +// Log.startProgress() +// val unzipper = new DecompressZip(filePath) +// +// unzipper.on('progress', function (fileIndex, fileCount) { +// Log.progress("Extracting Viper Tools", fileIndex + 1, fileCount, LogLevel.Debug) +// }) +// +// unzipper.on('error', function (e) { +// if (e.code && e.code == 'ENOENT') { +// Log.debug("Error updating the Viper Tools, missing create file permission in the viper tools directory: " + e) +// } else if (e.code && e.code == 'EACCES') { +// Log.debug("Error extracting " + filePath + ": " + e + " | " + e.message) +// } else { +// Log.debug("Error extracting " + filePath + ": " + e) +// } +// resolve(false) +// }) +// +// unzipper.on('extract', function (log) { +// resolve(true) +// }) +// +// unzipper.extract({ +// path: pathHelper.dirname(filePath), +// filter: function (file) { +// return file.type !== "SymbolicLink" +// } +// }) +// } catch (e) { +// Log.debug("Error extracting viper tools: " + e) +// resolve(false) +// } +// }) +// } +// def getParentDir(fileOrFolderPath: String): String = { +// if (!fileOrFolderPath) return null +// val obj = pathHelper.parse(fileOrFolderPath) +// if (obj.base) { +// return obj.dir +// } +// val folderPath = obj.dir +// val is_matching = folderPath.match(/(^.*)[\/\\].+$/) //the regex retrieves the parent directory +// if (is_matching) { +// if (is_matching[1] == fileOrFolderPath) { +// Log.debug("getParentDir has a fixpoint at " + fileOrFolderPath) +// return null +// } +// return match[1] +// } +// else { +// return null +// } +// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/Coordinator.scala b/src/main/scala/viper/server/Coordinator.scala index 3d83c97..78d7c33 100644 --- a/src/main/scala/viper/server/Coordinator.scala +++ b/src/main/scala/viper/server/Coordinator.scala @@ -2,31 +2,30 @@ package viper.server import java.util.concurrent.CompletableFuture -import org.eclipse.lsp4j.services.LanguageClient -import org.eclipse.lsp4j.{Position, TextDocumentItem, Range} +import org.eclipse.lsp4j.TextDocumentItem import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import scala.util.matching.Regex object Coordinator { var port: Int = _ var url: String = _ var client: IdeLanguageClient = _ - var backend: Backend - var tempDirectory: String = pathHelper.join(os.tmpdir(), ".vscode") = _ - var backendOutputDirectory: String = os.tmpdir() = _ +// var tempDirectory: String = pathHelper.join(os.tmpdir(), ".vscode") = _ +// var backendOutputDirectory: String = os.tmpdir() = _ var executedStages: ArrayBuffer[Stage] = _ var documents: TextDocumentItem = new TextDocumentItem() - var verificationTasks = mutable.Map.empty[String, VerificationTask] - var backendService: ViperServerService = _ + var files = mutable.Map.empty[String, FileManager] + var startingOrRestarting: Boolean = false + var backend: BackendProperties = _ + val verifier: ViperServerService = new ViperServerService(Array()) def getAddress: String = url + ":" + port def stage: Option[Stage] = { - if (executedStages != null && this.executedStages.length > 0) { + if (executedStages != null && this.executedStages.nonEmpty) { Some(executedStages(executedStages.length - 1)) } else { None @@ -35,10 +34,10 @@ object Coordinator { def canVerificationBeStarted(uri: String, manuallyTriggered: Boolean): Boolean = { //check if there is already a verification task for that file - if(verificationTasks.get(uri).isEmpty){ + if(files.get(uri).isEmpty){ Log.debug("No verification task found for file: " + uri) false - } else if (!backendService.isReady) { + } else if (!verifier.isReady) { if (manuallyTriggered) { Log.hint("The verification backend is not ready yet") } @@ -50,26 +49,26 @@ object Coordinator { } def stopAllRunningVerifications(): CompletableFuture[Void] = { - if (Coordinator.verificationTasks.nonEmpty) { - val promises = Coordinator.verificationTasks.values.map(task => task.abortVerification()).toSeq + if (Coordinator.files.nonEmpty) { + val promises = Coordinator.files.values.map(task => task.abortVerification()).toSeq CompletableFuture.allOf(promises:_*) } else { - CompletableFuture.completedFuture() + CompletableFuture.completedFuture(null) } } //Communication requests and notifications sent to language client - def sendStateChangeNotification(params: StateChangeParams, task: Option[VerificationTask]): Unit = { + def sendStateChangeNotification(params: StateChangeParams, task: Option[FileManager]): Unit = { if (task.isDefined) task.get.state = params.newState client.notifyStateChanged(params) } - def sendStepsAsDecorationOptions(decorations: StepsAsDecorationOptionsResult) = { - Log.log("Update the decoration options (" + decorations.decorationOptions.length + ")", LogLevel.Debug) - client.stepsAsDecorationOptions(decorations) - } - - def sendStartBackendMessage(backend: String, forceRestart: Boolean, isViperServer: Boolean) { - client.sendNotification(Commands.StartBackend, {backend: backend, forceRestart: forceRestart, isViperServer: isViperServer }) - } +// def sendStepsAsDecorationOptions(decorations: StepsAsDecorationOptionsResult) = { +// Log.log("Update the decoration options (" + decorations.decorationOptions.length + ")", LogLevel.Debug) +// client.stepsAsDecorationOptions(decorations) +// } +// +// def sendStartBackendMessage(backend: String, forceRestart: Boolean, isViperServer: Boolean) { +// client.sendNotification(Commands.StartBackend, {backend: backend, forceRestart: forceRestart, isViperServer: isViperServer }) +// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/DataProtocol.scala b/src/main/scala/viper/server/DataProtocol.scala index b5ef69c..daa143c 100644 --- a/src/main/scala/viper/server/DataProtocol.scala +++ b/src/main/scala/viper/server/DataProtocol.scala @@ -1,6 +1,6 @@ package viper.server -import org.eclipse.lsp4j.{Range, Position} +import org.eclipse.lsp4j.{Diagnostic, Position, Range} object VerificationSuccess extends Enumeration { type VerificationSuccess = Value @@ -11,7 +11,7 @@ object VerificationSuccess extends Enumeration { val Error = Value // Caused by veification taking too long val Timeout = Value } -import VerificationSuccess._ +import viper.server.VerificationSuccess._ object VerificationState extends Enumeration { type VerificationState = Value @@ -23,14 +23,14 @@ object VerificationState extends Enumeration { val Stopping = Value val Stage = Value } -import VerificationState._ +import viper.server.VerificationState._ object SettingsErrorType extends Enumeration { type SettingsErrorType = Value val Error, Warning = Value } -import SettingsErrorType._ +import viper.server.SettingsErrorType._ object LogLevel extends Enumeration { type LogLevel = Value @@ -43,7 +43,7 @@ object LogLevel extends Enumeration { val LowLevelDebug = Value // all output of used tools is written to logFile, } // some of it also to the console -case class Progress ( +case class ProgressReport ( domain: String, current: Double, total: Double, @@ -52,23 +52,24 @@ case class Progress ( case class Hint(msg: String, showButton1: Boolean, showButton2: Boolean) +//Might need to change assignments case class StateChangeParams( newState: VerificationState, - progress: Double = null, + manuallyTriggered: Boolean, //should be Boolean + verificationCompleted: Boolean, //should be Boolean + progress: Double = -1, success: VerificationSuccess = null, - verificationCompleted: Boolean = null, - manuallyTriggered: Boolean = null, filename: String = null, backendName: String = null, - time: Int = null, - nofErrors: Int = null, - verificationNeeded: Boolean = null, + time: Int = -1, + nofErrors: Int = -1, +// verificationNeeded: Double = Double.NaN, //should be Boolean uri: String = null, stage: String = null, error: String = null, - diagnostics: String = null) + diagnostics: Array[Diagnostic] = null) -case class Backend( +case class BackendProperties( name: String, backend_type: String, paths: Array[String], @@ -112,12 +113,12 @@ case class Versions( extensionVersion: String) case class ViperSettings( - viperServerSettings: ViperServerSettings, // All viperServer related settings - verificationBackends: Array[Backend], // Description of backends - paths: PathSettings, // Used paths - preferences: UserPreferences, // General user preferences - javaSettings: JavaSettings, // Java settings - advancedFeatures: AdvancedFeatureSettings) // Settings for AdvancedFeatures + viperServerSettings: ViperServerSettings, // All viperServer related settings + verificationBackends: Array[BackendProperties], // Description of backends + paths: PathSettings, // Used paths + preferences: UserPreferences, // General user preferences + javaSettings: JavaSettings, // Java settings + advancedFeatures: AdvancedFeatureSettings) // Settings for AdvancedFeatures case class Stage( name: String, //The per backend unique name of this stage @@ -189,11 +190,11 @@ case class BackendOutput( typ: String, name: String = null, backendType: String = null, - nofMethods: Int = null, - nofPredicates: Int = null, - nofFunctions: Int = null, //for End - time: Long = null, //for Error + nofMethods: Int = -1, + nofPredicates: Int = -1, + nofFunctions: Int = -1, //for End + time: Long = -1, //for Error file: String = null, errors: Array[Error] = null, //for Outlin - members: Array[Member] = null, //for Definitions +// members: Array[Member] = null, //for Definitions definitions: Array[Definition] = null) \ No newline at end of file diff --git a/src/main/scala/viper/server/VerificationTask.scala b/src/main/scala/viper/server/FileManager.scala similarity index 68% rename from src/main/scala/viper/server/VerificationTask.scala rename to src/main/scala/viper/server/FileManager.scala index e1c036e..7b8e59a 100644 --- a/src/main/scala/viper/server/VerificationTask.scala +++ b/src/main/scala/viper/server/FileManager.scala @@ -1,41 +1,35 @@ package viper.server -import java.nio.file.Paths import java.util.concurrent.{CompletableFuture => CFuture} -import akka.actor.Actor -import akka.util.{Timeout => AkkaTimeout} +import akka.actor.{Actor, Props} import org.eclipse.lsp4j.{Diagnostic, DiagnosticSeverity, Location, Position, PublishDiagnosticsParams, Range, SymbolInformation, SymbolKind} import viper.server.VerificationState._ import viper.server.VerificationSuccess._ -import viper.silver.ast.{Domain, Field, Method, Predicate, SourcePosition} -import viper.silver.reporter.{EntityFailureMessage, EntitySuccessMessage, OverallFailureMessage, OverallSuccessMessage, ProgramDefinitionsReport, ProgramOutlineReport, StatisticsReport} +import viper.silver.ast.{Domain, Field, Function, Method, Predicate, SourcePosition} +import viper.silver.reporter._ import scala.collection.JavaConverters._ -import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import scala.concurrent.Future - -class VerificationTask(fileUri: String) { - //state that is valid across verifications - var verificationCount: Int = 0 +class FileManager(fileUri: String) { // file under verification - var filename: String = _ var path: String = _ + var filename: String = _ var lastSuccess: VerificationSuccess = NA var internalErrorMessage: String = "" //state specific to one verification - var is_running: Boolean = false - var global_failure: Boolean = false var is_aborting: Boolean = false + var is_verifying: Boolean = false + var global_failure: Boolean = false var state: VerificationState = Stopped - var manuallyTriggered: Boolean + var manuallyTriggered: Boolean = _ //verification results + var jid: Int = -1 var time: Long = 0 - var diagnostics: mutable.ArrayBuffer[Diagnostic] = _ + var diagnostics: ArrayBuffer[Diagnostic] = _ // var verifiables: Array[Verifiable] = _ var parsingCompleted: Boolean = false var typeCheckingCompleted: Boolean = false @@ -44,14 +38,13 @@ class VerificationTask(fileUri: String) { // var shownExecutionTrace: Array[ExecutionTrace] = _ var symbolInformation: ArrayBuffer[SymbolInformation] = _ var definitions: ArrayBuffer[Definition] = _ - var manuallyTriggered: Boolean = _ //working variables private var lines: Array[String] = Array() private var wrongFormat: Boolean = false private var partialData: String = "" - def resetDiagnostics() = { + def resetDiagnostics(): Unit = { diagnostics = ArrayBuffer() val diagnosticParams = new PublishDiagnosticsParams() diagnosticParams.setUri(fileUri) @@ -59,12 +52,12 @@ class VerificationTask(fileUri: String) { Coordinator.client.publishDiagnostics(diagnosticParams) } - def resetLastSuccess() = { + def resetLastSuccess(): Unit = { lastSuccess = NA } - def prepareVerification() = { - is_running = true + def prepareVerification(): Unit = { + is_verifying = true is_aborting = false state = Stopped lines = Array() @@ -81,20 +74,51 @@ class VerificationTask(fileUri: String) { internalErrorMessage = "" } - def abortVerification(): CFuture[Unit] = { - if (!is_running) { - return CFuture.completedFuture() + def abortVerification(): CFuture[Void] = { + if (!is_verifying) { + return CFuture.completedFuture(null) } Log.info("Aborting running verification.") is_aborting = true - Coordinator.backendService.stopVerification().thenApply(_ => { - is_running = false + Coordinator.verifier.stopVerification(jid).thenAccept(_ => { + is_verifying = false lastSuccess = Aborted }).exceptionally(e => { Log.debug("Error aborting verification of " + filename + ": " + e) + null }) } + def startStageProcess(stage: Stage, fileToVerify: String): Option[String] = { + try { + Log.lowLevel("Start Stage Process") + Some(getStageCommand(fileToVerify, stage)) + } catch { + case e: Throwable => + Log.debug("Error starting stage process: " + e) + None + } + } + + def getStageCommand(fileToVerify: String, stage: Stage): String = { +// val args: String = getViperBackendClassName(stage) + " " + stage.customArguments +// val command = Settings.expandCustomArguments(args, stage, fileToVerify, Coordinator.backend) + val command = "silicon --cachingDisabled" + Log.debug(command) + command + } + + def getViperBackendClassName(stage: Stage): String = { + Coordinator.backend.backend_type match { + case "silicon" => "silicon" + case "carbon" => "carbon" + case "other" => stage.mainMethod + case _ => throw new Error(s"Invalid verification backend value. " + + s"Possible values are [silicon | carbon | other] " + + s"but found ${Coordinator.backend}") + } + } + def verify(manuallyTriggered: Boolean): Boolean = { prepareVerification() this.manuallyTriggered = manuallyTriggered @@ -102,7 +126,8 @@ class VerificationTask(fileUri: String) { // This should have exactly one stage val stage = Coordinator.backend.stages.head if (stage == null) { - Log.debug("backend " + Coordinator.backend.name + " has no " + Settings.VERIFY + " stage, even though the settigns were checked.") +// Log.debug("backend " + Coordinator.backend.name + " has no " + Settings.VERIFY + " stage, even though the settigns were checked.") + Log.debug("Should have exactly one stage") return false } @@ -111,19 +136,24 @@ class VerificationTask(fileUri: String) { filename = Common.filenameFromPath(path) Log.toLogFile("verify " + filename) - verificationCount += 1 Coordinator.executedStages.append(stage) Log.info(Coordinator.backend.name + " verification started") - val params = StateChangeParams(VerificationRunning, filename = filename) + val params = StateChangeParams(VerificationRunning, manuallyTriggered, false, filename = filename) Coordinator.sendStateChangeNotification(params, Some(this)) - startVerificationTimeout(verificationCount) - Coordinator.backendService.startStageProcess(stage, path, stdOutHandler, stdErrHandler, completionHandler) + val command_opt = startStageProcess(stage, path) + val command = command_opt.getOrElse(return false) + jid = Coordinator.verifier.startVerification(command) + Coordinator.verifier.startStreaming(jid, RelayActor.props(this)) true } - class RelayActor() extends Actor { + object RelayActor { + def props(task: FileManager): Props = Props(new RelayActor(task)) + } + + class RelayActor(task: FileManager) extends Actor { override def receive: PartialFunction[Any, Unit] = { case ProgramOutlineReport(members) => @@ -131,11 +161,11 @@ class VerificationTask(fileUri: String) { members.foreach(m => { val location: Location = new Location(fileUri, null) val kind = m match { - case Method => SymbolKind.Method - case Function => SymbolKind.Function - case Predicate => SymbolKind.Interface - case Field => SymbolKind.Field - case Domain => SymbolKind.Class + case _: Method => SymbolKind.Method + case _: Function => SymbolKind.Function + case _: Predicate => SymbolKind.Interface + case _: Field => SymbolKind.Field + case _: Domain => SymbolKind.Class case _ => SymbolKind.Enum } val info: SymbolInformation = new SymbolInformation(m.name, kind, location) @@ -166,19 +196,19 @@ class VerificationTask(fileUri: String) { case StatisticsReport(m, f, p, _, _) => // TODO: pass in task (probably as props to actor). progress = new Progress(p, f, m) - val params = StateChangeParams(VerificationRunning, progress = 0, filename = filename) - Coordinator.sendStateChangeNotification(params, this) + val params = StateChangeParams(VerificationRunning, manuallyTriggered, false, progress = 0, filename = filename) + Coordinator.sendStateChangeNotification(params, Some(task)) case EntitySuccessMessage(_, concerning, _, _) => if (progress == null) { Log.debug("The backend must send a VerificationStart message before the ...Verified message.") - return + } else { + val output = BackendOutput(BackendOutputType.FunctionVerified, name = concerning.name) + progress.updateProgress(output) + val progressPercentage = progress.toPercent + val params = StateChangeParams(VerificationRunning, manuallyTriggered, false, progress = progressPercentage, filename = filename) + Coordinator.sendStateChangeNotification(params, Some(task)) } - val output = BackendOutput(BackendOutputType.FunctionVerified, name = concerning.name) - progress.updateProgress(output) - val progressPercentage = progress.toPercent() - val params = StateChangeParams(VerificationRunning, progress = progressPercentage, filename = filename) - Coordinator.sendStateChangeNotification(params, this) - case EntityFailureMessage(ver, concerning, time, res, cached) => + case EntityFailureMessage(_, _, _, res, _) => res.errors.foreach(err => { if (err.fullId != null && err.fullId == "typechecker.error") { typeCheckingCompleted = false @@ -205,19 +235,20 @@ class VerificationTask(fileUri: String) { val diag = new Diagnostic(range, err.readableMessage + cachFlag, DiagnosticSeverity.Error, "") diagnostics.append(diag) - val params = StateChangeParams(VerificationRunning, filename = filename, nofErrors = diagnostics.length, uri = fileUri, diagnostics) - Coordinator.sendStateChangeNotification(params, this) + val params = StateChangeParams( + VerificationRunning, manuallyTriggered, false, filename = filename, + nofErrors = diagnostics.length,uri = fileUri, + diagnostics = diagnostics.toArray) + Coordinator.sendStateChangeNotification(params, Some(task)) //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) }) - case OverallSuccessMessage(ver, verificationTime) => + case OverallSuccessMessage(_, verificationTime) => state = VerificationReporting time = verificationTime - Coordinator.backendService.isSessionRunning = false completionHandler(0) - case OverallFailureMessage(ver, verificationTime, failure) => + case OverallFailureMessage(_, verificationTime, _) => state = VerificationReporting time = verificationTime - Coordinator.backendService.isSessionRunning = false completionHandler(0) case e: Throwable => //receiving an error means the promise can be finalized with failure. @@ -247,7 +278,7 @@ class VerificationTask(fileUri: String) { try { Log.debug("completionHandler is called with code ${code}") if (is_aborting) { - is_running = false + is_verifying = false return } var success = NA @@ -261,11 +292,9 @@ class VerificationTask(fileUri: String) { if (!isVerifyingStage) { success = Success - var params = StateChangeParams( - Ready, success = Success, manuallyTriggered = manuallyTriggered, - filename = filename, nofErrors = 0, time = time.toInt, - verificationCompleted = false, uri = fileUri, - error = internalErrorMessage) + val params = StateChangeParams( + Ready, manuallyTriggered, false, success = Success, filename = filename, + nofErrors = 0, time = time.toInt, uri = fileUri, error = internalErrorMessage) Coordinator.sendStateChangeNotification(params, Some(this)) } else { if (partialData.length > 0) { @@ -277,7 +306,7 @@ class VerificationTask(fileUri: String) { //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) //inform client about postProcessing - var params = StateChangeParams(PostProcessing, filename = filename) + var params = StateChangeParams(PostProcessing, manuallyTriggered, true, filename = filename) Coordinator.sendStateChangeNotification(params, Some(this)) //notify client about outcome of verification @@ -287,23 +316,23 @@ class VerificationTask(fileUri: String) { Coordinator.sendStateChangeNotification(params, Some(this)) if (code != 0 && code != 1 && code != 899) { - Log.debug("Verification Backend Terminated Abnormaly: with code " + code) + Log.debug("Verification Backend Terminated Abnormally: with code " + code) } } //reset for next verification lastSuccess = success time = 0 - is_running = false + is_verifying = false } catch { case e: Throwable => - is_running = false + is_verifying = false Coordinator.client.notifyVerificationNotStarted(fileUri) Log.debug("Error handling verification completion: ", e) } } - private def startVerificationTimeout(verificationCount: Int) = { +// private def startVerificationTimeout(verificationCount: Int) = { // if (Coordinator.backend.timeout > 0) { // Log.lowLevel("Set verification timeout to " + Coordinator.backend.timeout) // @@ -324,5 +353,5 @@ class VerificationTask(fileUri: String) { // } else { // Log.lowLevel("No verification timeout set") // } - } +// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/IdeLanguageClient.scala b/src/main/scala/viper/server/IdeLanguageClient.scala index 37ea26f..2cc1c8b 100644 --- a/src/main/scala/viper/server/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/IdeLanguageClient.scala @@ -2,9 +2,9 @@ package viper.server import java.util.concurrent.CompletableFuture -import org.eclipse.lsp4j.{Diagnostic, Position} -import org.eclipse.lsp4j.services.LanguageClient +import org.eclipse.lsp4j.Position import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} +import org.eclipse.lsp4j.services.LanguageClient trait IdeLanguageClient extends LanguageClient { @@ -27,13 +27,13 @@ trait IdeLanguageClient extends LanguageClient { def notifyBackendChanged(name: String) @JsonNotification(S2C_Commands.Progress) - def notifyProgress(progress: Progress, logLevel: Int) + def notifyProgress(progress: ProgressReport, logLevel: Int) @JsonNotification(S2C_Commands.Log) def notifyLog(msg: String, logLevel: Int) @JsonNotification(S2C_Commands.Hint) - def notifyHint(msg: String, logLevel: Int) + def notifyHint(msg: String, hint: Hint) @JsonNotification(S2C_Commands.VerificationNotStarted) def notifyVerificationNotStarted(uri: String) diff --git a/src/main/scala/viper/server/IdeLog.scala b/src/main/scala/viper/server/IdeLog.scala index 69899f4..2bfea14 100644 --- a/src/main/scala/viper/server/IdeLog.scala +++ b/src/main/scala/viper/server/IdeLog.scala @@ -1,6 +1,6 @@ package viper.server -import LogLevel._ +import viper.server.LogLevel._ object Log { def log(message: String, logLevel: LogLevel) = { @@ -27,7 +27,7 @@ object Log { val progress = 100.0 * cur / len if (Math.floor(progress) > lastProgress) { lastProgress = progress - val data = Progress(domain, cur, len, progress, null) + val data = ProgressReport(domain, cur, len, progress, Double.NaN) Coordinator.client.notifyProgress(data, logLevel.id) } } diff --git a/src/main/scala/viper/server/LanguageServerReceiver.scala b/src/main/scala/viper/server/LanguageServerReceiver.scala index 38f86b9..1f84a4c 100644 --- a/src/main/scala/viper/server/LanguageServerReceiver.scala +++ b/src/main/scala/viper/server/LanguageServerReceiver.scala @@ -1,17 +1,15 @@ package viper.server -import LogLevel._ import java.util.concurrent.{CompletableFuture => CFuture} -import scala.collection.JavaConverters._ import com.google.gson.JsonPrimitive import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} -import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionOptions, CompletionParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, MessageParams, MessageType, Range, ServerCapabilities, ShowMessageRequestParams, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware} +import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams} +import viper.server.LogLevel._ +import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} class LanguageServerReceiver extends LanguageClientAware { @@ -23,6 +21,7 @@ class LanguageServerReceiver extends LanguageClientAware { // always send full text document for each notification: // capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) // capabilities.setCompletionProvider(new CompletionOptions(true, null)) + capabilities.setDefinitionProvider(true) capabilities.setDocumentSymbolProvider(true) CFuture.completedFuture(new InitializeResult(capabilities)) @@ -34,12 +33,12 @@ class LanguageServerReceiver extends LanguageClientAware { val uri = params.getTextDocument.getUri Common.isViperSourceFile(uri).thenAccept(isViperFile => { if (isViperFile) { - //notify client; + //notify client Coordinator.client.notifyFileOpened(uri) - if (!Coordinator.verificationTasks.contains(uri)) { + if (!Coordinator.files.contains(uri)) { //create new task for opened file - val task = new VerificationTask(uri) - Coordinator.verificationTasks += (uri -> task) + val manager = new FileManager(uri) + Coordinator.files += (uri -> manager) } } }) @@ -50,10 +49,10 @@ class LanguageServerReceiver extends LanguageClientAware { @JsonNotification("textDocument/didChange") def onDidChangeDocument(params: DidChangeTextDocumentParams): Unit = { - val task_opt = Coordinator.verificationTasks.get(params.getTextDocument.getUri) - val task: VerificationTask = task_opt.getOrElse(return) - task.symbolInformation = ArrayBuffer() - task.definitions = ArrayBuffer() + val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) + val manager: FileManager = manager_opt.getOrElse(return) + manager.symbolInformation = ArrayBuffer() + manager.definitions = ArrayBuffer() } @JsonNotification("textDocument/didClose") @@ -70,17 +69,19 @@ class LanguageServerReceiver extends LanguageClientAware { @JsonNotification(S2C_Commands.FileClosed) def onFileClosed(uri: String): Unit = { - val task_opt = Coordinator.verificationTasks.get(uri) - val task = task_opt.getOrElse(return) - task.resetDiagnostics() - Coordinator.verificationTasks -= uri + val manager_opt = Coordinator.files.get(uri) + val manager = manager_opt.getOrElse(return) + manager.resetDiagnostics() + Coordinator.files -= uri } @JsonRequest("textDocument/documentSymbol") - def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[SymbolInformation] = { - val task_opt = Coordinator.verificationTasks.get(params.getTextDocument.getUri) - val task = task_opt.getOrElse(return CFuture.completedFuture(Array())) - CFuture.completedFuture(task.symbolInformation) + def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[List[SymbolInformation]] = { + var symbolInfo_list: List[SymbolInformation] = List() + val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) + val manager = manager_opt.getOrElse(return CFuture.completedFuture(symbolInfo_list)) + symbolInfo_list = manager.symbolInformation.toList + CFuture.completedFuture(symbolInfo_list) } @JsonRequest("textDocument/definition") @@ -88,14 +89,14 @@ class LanguageServerReceiver extends LanguageClientAware { Log.log("Handling definitions request for params: " + params.toString, LogLevel.Debug) val document = params.getTextDocument val pos = params.getPosition - val task_opt = Coordinator.verificationTasks.get(params.getTextDocument.getUri) + val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) - task_opt match { - case Some(task) => + manager_opt match { + case Some(manager) => Log.log("Found verification task for URI " + document.getUri, LogLevel.LowLevelDebug) Coordinator.client.requestIdentifier(pos).thenApply(word => { Log.log("Got word: " + word, LowLevelDebug) - task.definitions.filter(d => (d.scope.getStart == null) //is global + manager.definitions.filter(d => (d.scope.getStart == null) //is global || (Common.comparePosition(d.scope.getStart, pos) <= 0 && Common.comparePosition(d.scope.getEnd, pos) >= 0)) // in scope .filter(d => word == d.name) @@ -112,23 +113,26 @@ class LanguageServerReceiver extends LanguageClientAware { @JsonRequest(C2S_Commands.RemoveDiagnostics) def onRemoveDiagnostics(uri: String): CFuture[Boolean] = { - val task_opt = Coordinator.verificationTasks.get(uri) - val task = task_opt.getOrElse(return CFuture.completedFuture(false)) - task.resetDiagnostics() + val manager_opt = Coordinator.files.get(uri) + val manager = manager_opt.getOrElse(return CFuture.completedFuture(false)) + manager.resetDiagnostics() CFuture.completedFuture(true) } @JsonRequest("GetLanguageServerUrl") def onGetServerUrl(): CFuture[String] = { - CFuture.completedFuture(Coordinator.getAddress()) + CFuture.completedFuture(Coordinator.getAddress) } @JsonNotification(C2S_Commands.SwapBackend) def onSwapBackend(backendName: String): Unit = { try { - Coordinator.backendService.swapBackend(Settings.getBackend(backendName)); + val b = BackendProperties( + "new Backend", backendName, null, null, + 5000, null, 5000, null) + Coordinator.verifier.swapBackend(b) } catch { - case e: Throwable => Log.debug("Error handling swap backend request: " + e); + case e: Throwable => Log.debug("Error handling swap backend request: " + e) } } @@ -136,13 +140,13 @@ class LanguageServerReceiver extends LanguageClientAware { def onVerify(data: VerifyRequest): Unit = { //it does not make sense to reverify if no changes were made and the verification is already running if (Coordinator.canVerificationBeStarted(data.uri, data.manuallyTriggered)) { - Settings.workspace = data.workspace - Log.log("start or restart verification", LogLevel.Info); +// Settings.workspace = data.workspace + Log.log("start or restart verification", LogLevel.Info) //stop all other verifications because the backend crashes if multiple verifications are run in parallel Coordinator.stopAllRunningVerifications().thenAccept(_ => { //start verification Coordinator.executedStages = ArrayBuffer() - val hasVerificationstarted = Coordinator.verificationTasks + val hasVerificationstarted = Coordinator.files .getOrElse(data.uri, return) .verify(data.manuallyTriggered) if (!hasVerificationstarted) { @@ -162,10 +166,6 @@ class LanguageServerReceiver extends LanguageClientAware { @JsonNotification(C2S_Commands.FlushCache) def flushCache(file: String): Unit = { println("flushing cache...") -// _client match { -// case Some(c) => c.showMessage(new MessageParams(MessageType.Info, s"$file got flushed :>")) -// case _ => -// } } @JsonRequest(value = "shutdown") @@ -196,15 +196,14 @@ class LanguageServerReceiver extends LanguageClientAware { def completionItemResolve(item: CompletionItem): CFuture[CompletionItem] = { val data: Object = item.getData data match { - case n: JsonPrimitive if n.getAsInt == 1 => { + case n: JsonPrimitive if n.getAsInt == 1 => item.setDetail("TypeScript details") item.setDocumentation("TypeScript documentation") - } - case n: JsonPrimitive if n.getAsInt == 2 => { + case n: JsonPrimitive if n.getAsInt == 2 => item.setDetail("JavaScript details") item.setDocumentation("JavaScript documentation") - } - case _ => item.setDetail(s"${data.toString} is instance of ${data.getClass}") + case _ => + item.setDetail(s"${data.toString} is instance of ${data.getClass}") } CFuture.completedFuture(item) } diff --git a/src/main/scala/viper/server/Progress.scala b/src/main/scala/viper/server/Progress.scala index d8234c0..ea6b830 100644 --- a/src/main/scala/viper/server/Progress.scala +++ b/src/main/scala/viper/server/Progress.scala @@ -1,5 +1,3 @@ -package viper.server - /** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -7,6 +5,9 @@ package viper.server * * Copyright (c) 2011-2019 ETH Zurich. */ + +package viper.server + class Progress(val nofPredicates: Int, val nofFunctions: Int, val nofMethods: Int) { var currentFunctions = 0; @@ -25,7 +26,7 @@ class Progress(val nofPredicates: Int, val nofFunctions: Int, val nofMethods: In } } - def toPercent(): Double = { + def toPercent: Double = { val total = nofFunctions + nofMethods + nofPredicates val current = currentFunctions + currentMethods + currentPredicates 100 * current / total diff --git a/src/main/scala/viper/server/Settings.scala b/src/main/scala/viper/server/Settings.scala index 7c1f009..b3c899f 100644 --- a/src/main/scala/viper/server/Settings.scala +++ b/src/main/scala/viper/server/Settings.scala @@ -1,232 +1,234 @@ -package viper.server - -import java.util.concurrent.CompletableFuture - -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} - -case class ResolvedPath (path: String, exists: Boolean, error: Option[String]) - +//package viper.server +// +//import java.util.concurrent.CompletableFuture +// +//import scala.concurrent.{ExecutionContext, Future} +//import scala.util.{Failure, Success} +// +//case class ResolvedPath (path: String, exists: Boolean, error: Option[String]) +// object Settings { - implicit val ex = ExecutionContext.global - var settings: ViperSettings - var isWin = System.getProperties.get("os.name") - var isLinux = /^linux/.test(process.platform) - var isMac = /^darwin/.test(process.platform) - var workspace: String = _ - var VERIFY = "verify" - var selectedBackend: String - - private var firstSettingsCheck = true - - private var _valid: Boolean = false - private var _errors: Array[SettingsError] = Array() - private var _upToDate: Boolean = false - - private var home = os.homedir(); - - def getStage(backend: Backend, name: Option[String]): Option[Stage] = { - for { - n <- name - res <- backend.stages.find(_.name == n) - } yield res - } - - def getStageFromSuccess(backend: Backend, stage: Stage, success: Success): Stage = { - switch (success) { - case VerificationSuccess.ParsingFailed: - return this.getStage(backend, stage.onParsingError); - case VerificationSuccess.VerificationFailed: - return this.getStage(backend, stage.onVerificationError); - case VerificationSuccess.TypecheckingFailed: - return this.getStage(backend, stage.onTypeCheckingError); - case VerificationSuccess.Success: - return this.getStage(backend, stage.onSuccess); - } - return null; - } - - def backendEquals(a: Backend, b: Backend): Boolean = { - var areEqual: Boolean = a.stages.length == b.stages.length - areEqual = areEqual && a.name == b.name - areEqual = areEqual && a.backend_type == b.backend_type - areEqual = areEqual && a.timeout == b.timeout - areEqual = areEqual && this.resolveEngine(a.engine) == this.resolveEngine(b.engine); - a.stages.forEach((element, i) => { - areEqual = areEqual && this.stageEquals(element, b.stages[i]); - }); - areEqual = areEqual && a.paths.length == b.paths.length; - for (var i = 0; i < a.paths.length; i++) { - areEqual = areEqual && a.paths[i] == b.paths[i]; - } - return areEqual; - } - - def backendEquals(one: Option[Backend], other: Option[Backend]): Option[Boolean] = { - for { - a <- one - b <- other - } yield backendEquals(a, b) - } - - private def resolveEngine(engine: String) { - if (engine && (engine.toLowerCase() == "viperserver")) { - return engine; - } else { - return "none"; - } - } - - def useViperServer(backend: Backend) { - if (!backend || !backend.engine) return false; - return backend.engine.toLowerCase() == "viperserver"; - } - - private def stageEquals(a: Stage, b: Stage): Boolean { - var same = a.customArguments == b.customArguments; - same = same && a.mainMethod == b.mainMethod; - same = same && a.name == b.name; - same = same && a.isVerification == b.isVerification; - same = same && a.onParsingError == b.onParsingError; - same = same && a.onTypeCheckingError == b.onTypeCheckingError; - same = same && a.onVerificationError == b.onVerificationError; - same = same && a.onSuccess == b.onSuccess; - return same; - } - - def expandCustomArguments(args: String, stage: Stage, fileToVerify: String, backend: Backend): String { - //Log.log("Command before expanding: " + args,LogLevel.LowLevelDebug); - args = args.replace(/\s+/g, ' '); //remove multiple spaces - args = args.replace(/\$z3Exe\$/g, '"' + this.settings.paths.z3Executable + '"'); - args = args.replace(/\$boogieExe\$/g, '"' + this.settings.paths.boogieExecutable + '"'); - args = args.replace(/\$mainMethod\$/g, stage.mainMethod); - args = args.replace(/\$backendPaths\$/g, Settings.backendJars(backend)); - args = args.replace(/\$disableCaching\$/g, (Settings.settings.viperServerSettings.disableCaching === true ? "--disableCaching" : "")); - args = args.replace(/\$fileToVerify\$/g, '"' + fileToVerify + '"'); - args = args.replace(/\s+/g, ' '); //remove multiple spaces - //Log.log("Command after expanding: " + args.trim(),LogLevel.LowLevelDebug); - - return args.trim(); - } - - def expandViperToolsPath(path: String): String = { - if (!path) return path - if (typeof Settings.settings.paths.viperToolsPath !== "String") { - return path - } -// path = path.replace(/\$viperTools\$/g, [String>Settings.settings.paths.viperToolsPath) - return path - } - - def selectBackend(settings: ViperSettings, selectedBackend: String): Backend = { - if (selectedBackend != null) { - Settings.selectedBackend = selectedBackend; - } - if (!settings || !settings.verificationBackends || settings.verificationBackends.length == 0) { - this.selectedBackend = null; - return null; - } - if (this.selectedBackend) { -// for (var i = 0; i < settings.verificationBackends.length; i++) { -// var backend = settings.verificationBackends[i]; -// if (backend.name === this.selectedBackend) { -// return backend; -// } -// } - } - this.selectedBackend = settings.verificationBackends[0].name; - return settings.verificationBackends[0]; - } - - def getBackendNames(settings: ViperSettings): Array[String] { - var backendNames = []; - settings.verificationBackends.forEach((backend) => { - backendNames.push(backend.name); - }) - return backendNames; - } - - def getBackend(backendName: String): Backend { - return Settings.settings.verificationBackends.find(b => { return b.name == backendName }); - } - - def valid(): Boolean { - LanguageServerState.sendSettingsCheckedNotification({ ok: this._valid, errors: this._errors, settings: this.settings }); - return this._valid; - } - - def upToDate(): Boolean { - return this._upToDate; - } - - private def viperServerRelatedSettingsChanged(oldSettings: ViperSettings) { - if (!oldSettings) return true; - if ((Array[[String]]oldSettings.viperServerSettings.serverJars).length != (Array[[String]]this.settings.viperServerSettings.serverJars).length) - return true; - (Array[[String]]oldSettings.viperServerSettings.serverJars).forEach((path, index) => { - if (path != (Array[[String]]this.settings.viperServerSettings.serverJars)[index]) { - return true; - } - }) - if (oldSettings.viperServerSettings.backendSpecificCache != this.settings.viperServerSettings.backendSpecificCache - || oldSettings.viperServerSettings.customArguments != this.settings.viperServerSettings.customArguments - //|| oldSettings.viperServerSettings.disableCaching != this.settings.viperServerSettings.disableCaching //no need to restart the ViperServer if only that changes - || oldSettings.viperServerSettings.timeout != this.settings.viperServerSettings.timeout - ) { - return true; - } - Log.log("ViperServer settings did not change", LogLevel.LowLevelDebug); - return false; - } - - //tries to restart backend, - def initiateBackendRestartIfNeeded(oldSettings?: ViperSettings, selectedBackend?: String, viperToolsUpdated: Boolean = false) { - Settings.checkSettings(viperToolsUpdated).then(() => { - if (Settings.valid()) { - var newBackend = Settings.selectBackend(Settings.settings, selectedBackend); - - if (newBackend) { - //only restart the backend after settings changed if the active backend was affected - - Log.log("check if restart needed", LogLevel.LowLevelDebug); - var backendChanged = !Settings.backendEquals(Coordinator.backend, newBackend) //change in backend - var mustRestartBackend = !Coordinator.backendService.isReady() //backend is not ready -> restart - || viperToolsUpdated //Viper Tools Update might have modified the binaries - || (Coordinator.backendService.isViperServerService != this.useViperServer(newBackend)) //the new backend requires another engine type - || (Settings.useViperServer(newBackend) && this.viperServerRelatedSettingsChanged(oldSettings)) // the viperServerSettings changed - if (mustRestartBackend || backendChanged) { - Log.log(`Change Backend: from ${LanguageServerState.backend ? LanguageServerState.backend.name : "No Backend"} to ${newBackend ? newBackend.name : "No Backend"}`, LogLevel.Info); - Coordinator.backend = newBackend; - Coordinator.verificationTasks.forEach(task => task.resetLastSuccess()); - Coordinator.sendStartBackendMessage(Coordinator.backend.name, mustRestartBackend, Settings.useViperServer(newBackend)); - } else { - Log.log("No need to restart backend. It is still the same", LogLevel.Debug) - Coordinator.backend = newBackend; - Coordinator.sendBackendReadyNotification({ - name: Coordinator.backend.name, - restarted: false, - isViperServer: Settings.useViperServer(newBackend) - }); - } - } else { - Log.debug("No backend, even though the setting check succeeded."); - } - } else { - Coordinator.backendService.stop(); - } - }); - } - - private def addError(msg: String) { - this._errors.push({ type: SettingsErrorType.Error, msg: msg }); - } - private def addErrors(errors: SettingsError[]) { - this._errors = this._errors.concat(errors); - } - private def addWarning(msg: String) { - this._errors.push({ type: SettingsErrorType.Warning, msg: msg }); - } - +// implicit val ex = ExecutionContext.global +// var settings: ViperSettings +// var isWin = System.getProperties.get("os.name") +// var isLinux = /^linux/.test(process.platform) +// var isMac = /^darwin/.test(process.platform) +// var workspace: String = _ +// var VERIFY = "verify" +// var selectedBackend: String +// +// private var firstSettingsCheck = true +// +// private var _valid: Boolean = false +// private var _errors: Array[SettingsError] = Array() +// private var _upToDate: Boolean = false +// +// private var home = os.homedir(); +// +// def getStage(backend: BackendProperties, name: Option[String]): Option[Stage] = { +// for { +// n <- name +// res <- backend.stages.find(_.name == n) +// } yield res +// } +// +// def getStageFromSuccess(backend: BackendProperties, stage: Stage, success: Success): Stage = { +// switch (success) { +// case VerificationSuccess.ParsingFailed: +// return this.getStage(backend, stage.onParsingError); +// case VerificationSuccess.VerificationFailed: +// return this.getStage(backend, stage.onVerificationError); +// case VerificationSuccess.TypecheckingFailed: +// return this.getStage(backend, stage.onTypeCheckingError); +// case VerificationSuccess.Success: +// return this.getStage(backend, stage.onSuccess); +// } +// return null; +// } +// +// def backendEquals(a: BackendProperties, b: BackendProperties): Boolean = { +// var areEqual: Boolean = a.stages.length == b.stages.length +// areEqual = areEqual && a.name == b.name +// areEqual = areEqual && a.backend_type == b.backend_type +// areEqual = areEqual && a.timeout == b.timeout +// areEqual = areEqual && this.resolveEngine(a.engine) == this.resolveEngine(b.engine); +// a.stages.forEach((element, i) => { +// areEqual = areEqual && this.stageEquals(element, b.stages[i]); +// }); +// areEqual = areEqual && a.paths.length == b.paths.length; +// for (var i = 0; i < a.paths.length; i++) { +// areEqual = areEqual && a.paths[i] == b.paths[i]; +// } +// return areEqual; +// } +// +// def backendEquals(one: Option[BackendProperties], other: Option[BackendProperties]): Option[Boolean] = { +// for { +// a <- one +// b <- other +// } yield backendEquals(a, b) +// } +// +// private def resolveEngine(engine: String) { +// if (engine && (engine.toLowerCase() == "viperserver")) { +// return engine; +// } else { +// return "none"; +// } +// } +// +// def useViperServer(backend: BackendProperties) { +// if (!backend || !backend.engine) return false; +// return backend.engine.toLowerCase() == "viperserver"; +// } +// +// private def stageEquals(a: Stage, b: Stage): Boolean = { +// var same = a.customArguments == b.customArguments; +// same = same && a.mainMethod == b.mainMethod; +// same = same && a.name == b.name; +// same = same && a.isVerification == b.isVerification; +// same = same && a.onParsingError == b.onParsingError; +// same = same && a.onTypeCheckingError == b.onTypeCheckingError; +// same = same && a.onVerificationError == b.onVerificationError; +// same = same && a.onSuccess == b.onSuccess; +// return same; +// } +// +// def expandCustomArguments(args: String, stage: Stage, fileToVerify: String, backend: BackendProperties): String = { +// //Log.log("Command before expanding: " + args,LogLevel.LowLevelDebug); +// args = args.replace(/\s+/g, ' '); //remove multiple spaces +// args = args.replace(/\$z3Exe\$/g, '"' + this.settings.paths.z3Executable + '"'); +// args = args.replace(/\$boogieExe\$/g, '"' + this.settings.paths.boogieExecutable + '"'); +// args = args.replace(/\$mainMethod\$/g, stage.mainMethod); +// args = args.replace(/\$backendPaths\$/g, Settings.backendJars(backend)); +// args = args.replace(/\$disableCaching\$/g, (Settings.settings.viperServerSettings.disableCaching === true ? "--disableCaching" : "")); +// args = args.replace(/\$fileToVerify\$/g, '"' + fileToVerify + '"'); +// args = args.replace(/\s+/g, ' '); //remove multiple spaces +// //Log.log("Command after expanding: " + args.trim(),LogLevel.LowLevelDebug); +// +// return args.trim(); +// } +// +// def expandViperToolsPath(path: String): String = { +// if (!path) return path +// if (typeof Settings.settings.paths.viperToolsPath !== "String") { +// return path +// } +//// path = path.replace(/\$viperTools\$/g, [String>Settings.settings.paths.viperToolsPath) +// return path +// } +// +// def selectBackend(settings: ViperSettings, selectedBackend: String): BackendProperties = { +// if (selectedBackend != null) { +// Settings.selectedBackend = selectedBackend; +// } +// if (!settings || !settings.verificationBackends || settings.verificationBackends.length == 0) { +// this.selectedBackend = null; +// return null; +// } +// if (this.selectedBackend) { +//// for (var i = 0; i < settings.verificationBackends.length; i++) { +//// var backend = settings.verificationBackends[i]; +//// if (backend.name === this.selectedBackend) { +//// return backend; +//// } +//// } +// } +// this.selectedBackend = settings.verificationBackends[0].name; +// return settings.verificationBackends[0]; +// } +// +// def getBackendNames(settings: ViperSettings): Array[String] = { +// var backendNames = []; +// settings.verificationBackends.forEach((backend) => { +// backendNames.push(backend.name); +// }) +// return backendNames; +// } +// +// def getBackend(backendName: String): BackendProperties = { +// return Settings.settings.verificationBackends.find(b => { return b.name == backendName }); +// } +// +// def valid(): Boolean = { +// LanguageServerState.sendSettingsCheckedNotification({ ok: this._valid, errors: this._errors, settings: this.settings }); +// return this._valid; +// } +// +// def upToDate(): Boolean = { +// return this._upToDate; +// } +// +// private def viperServerRelatedSettingsChanged(oldSettings: ViperSettings) = { +// if (!oldSettings) return true; +// if ((Array[[String]]oldSettings.viperServerSettings.serverJars).length != (Array[[String]]this.settings.viperServerSettings.serverJars).length) +// return true; +// (Array[[String]]oldSettings.viperServerSettings.serverJars).forEach((path, index) => { +// if (path != (Array[[String]]this.settings.viperServerSettings.serverJars)[index]) { +// return true; +// } +// }) +// if (oldSettings.viperServerSettings.backendSpecificCache != this.settings.viperServerSettings.backendSpecificCache +// || oldSettings.viperServerSettings.customArguments != this.settings.viperServerSettings.customArguments +// //|| oldSettings.viperServerSettings.disableCaching != this.settings.viperServerSettings.disableCaching //no need to restart the ViperServer if only that changes +// || oldSettings.viperServerSettings.timeout != this.settings.viperServerSettings.timeout +// ) { +// return true; +// } +// Log.log("ViperServer settings did not change", LogLevel.LowLevelDebug); +// return false; +// } +// +// //tries to restart backend, +// def initiateBackendRestartIfNeeded(oldSettings?: ViperSettings, selectedBackend?: String, viperToolsUpdated: Boolean = false) { +// Settings.checkSettings(viperToolsUpdated).then(() => { +// if (Settings.valid()) { +// var newBackend = Settings.selectBackend(Settings.settings, selectedBackend); +// +// if (newBackend) { +// //only restart the backend after settings changed if the active backend was affected +// +// Log.log("check if restart needed", LogLevel.LowLevelDebug); +// var backendChanged = !Settings.backendEquals(Coordinator.backend, newBackend) //change in backend +// var mustRestartBackend = !Coordinator.verifier.isReady() //backend is not ready -> restart +// || viperToolsUpdated //Viper Tools Update might have modified the binaries +// || (Coordinator.verifier.isViperServerService != this.useViperServer(newBackend)) //the new backend requires another engine type +// || (Settings.useViperServer(newBackend) && this.viperServerRelatedSettingsChanged(oldSettings)) // the viperServerSettings changed +// if (mustRestartBackend || backendChanged) { +// Log.log(`Change Backend: from ${LanguageServerState.backend ? LanguageServerState.backend.name : "No Backend"} to ${newBackend ? newBackend.name : "No Backend"}`, LogLevel.Info); +// Coordinator.backend = newBackend; +// Coordinator.files.forEach(task => task.resetLastSuccess()); +// Coordinator.sendStartBackendMessage(Coordinator.backend.name, mustRestartBackend, Settings.useViperServer(newBackend)); +// } else { +// Log.log("No need to restart backend. It is still the same", LogLevel.Debug) +// Coordinator.backend = newBackend; +// Coordinator.sendBackendReadyNotification({ +// name: Coordinator.backend.name, +// restarted: false, +// isViperServer: Settings.useViperServer(newBackend) +// }); +// } +// } else { +// Log.debug("No backend, even though the setting check succeeded."); +// } +// } else { +// Coordinator.verifier.stop(); +// } +// }); +// } +// +// private def addError(msg: String) { +// this._errors.push({ type: SettingsErrorType.Error, msg: msg }); +// } +// +// private def addErrors(errors: SettingsError[]) { +// this._errors = this._errors.concat(errors); +// } +// +// private def addWarning(msg: String) { +// this._errors.push({ type: SettingsErrorType.Warning, msg: msg }); +// } +// // private def checkSettingsVersion(settings: AnyRef, requiredVersions: AnyRef): Array[String] = { // var oldSettings = Array() // //check the settings versions @@ -257,9 +259,9 @@ object Settings { // } // return oldSettings; // } - - - +// +// +// // def checkSettings(viperToolsUpdated: Boolean): Future[Boolean] = { // try { // _valid = false; @@ -407,7 +409,7 @@ object Settings { // } // }) // } - +// // private def mergeBackend(custom: Backend, def: Backend) = { // if (!custom || !def || custom.name != def.name) return; // if (!custom.paths) custom.paths = def.paths; @@ -455,437 +457,437 @@ object Settings { // //TODO: check url format // return StringURL; // } - - private def checkPaths(paths: (String | Array[String] | PlatformDependentPath | PlatformDependentListOfPaths), prefix: String): Array[String] = { - //Log.log("checkPaths(" + JSON.Stringify(paths) + ")", LogLevel.LowLevelDebug); - var result: Array[String] = [] - var StringPaths: Array[String] = [] - if (!paths) { - this.addError(prefix + " paths are missing"); - } else if (typeof paths === "String") { - StringPaths.push(paths) - } else if (paths instanceof Array) { - paths.forEach(path => { - if (typeof path === "String") { - StringPaths.push(path) - } - }) - } else { - var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>paths; - if (Settings.isLinux) { - return this.checkPaths(platformDependentPath.linux, prefix); - } else if (Settings.isMac) { - return this.checkPaths(platformDependentPath.mac, prefix); - } else if (Settings.isWin) { - return this.checkPaths(platformDependentPath.windows, prefix); - } else { - Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); - } - return result; - } - - if (StringPaths.length == 0) { - this.addError(prefix + ' path has wrong type: expected: String | Array[String] | {windows:(String|Array[String]), mac:(String|Array[String]), linux:(String|Array[String])}, found: ' + typeof paths + " at path: " + JSON.Stringify(paths)); - } - - //resolve the paths - StringPaths = StringPaths.map(StringPath => { - var resolvedPath = Settings.resolvePath(StringPath, false); - if (!resolvedPath.exists) { - this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); - } - return resolvedPath.path - }); - if (StringPaths.length == 0) { - this.addError(prefix + ' no file found at at path: ' + JSON.Stringify(paths)); - } - //Log.log("checkPaths result: (" + JSON.Stringify(StringPaths) + ")", LogLevel.LowLevelDebug); - return StringPaths; - } - - private def checkPath(path: (String | PlatformDependentPath), prefix: String, executable: Boolean, allowPlatformDependentPath: Boolean, allowStringPath: Boolean = true, allowMissingPath = false): ResolvedPath = { - if (!path) { - if (!allowMissingPath) this.addError(prefix + " path is missing"); - return { path: null, exists: false }; - } - var StringPath: String; - if (typeof path === "String") { - if (!allowStringPath) { - this.addError(prefix + ' path has wrong type: expected: {windows:String, mac:String, linux:String}, found: ' + typeof path); - return { path: StringPath, exists: false }; - } - StringPath = [String>path; - } else { - if (!allowPlatformDependentPath) { - this.addError(prefix + ' path has wrong type: expected: String, found: ' + typeof path + " at path: " + JSON.Stringify(path)); - return { path: null, exists: false }; - } - var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>path; - if (Settings.isLinux) { - StringPath = platformDependentPath.linux; - } else if (Settings.isMac) { - StringPath = platformDependentPath.mac; - } else if (Settings.isWin) { - StringPath = platformDependentPath.windows; - } else { - Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); - } - } - - if (!StringPath || StringPath.length == 0) { - if (!allowMissingPath) { - this.addError(prefix + ' path has wrong type: expected: String' + (executable ? ' or {windows:String, mac:String, linux:String}' : "") + ', found: ' + typeof path + " at path: " + JSON.Stringify(path)); - } - return { path: StringPath, exists: false }; - } - var resolvedPath = Settings.resolvePath(StringPath, executable); - if (!resolvedPath.exists) { - this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); - } - return resolvedPath; - } - - private def checkBackends(backends: Backend[]) { - //Log.log("Checking backends...", LogLevel.Debug); - if (!backends || backends.length == 0) { - this.addError("No backend detected, specify at least one backend"); - return; - } - - var backendNames: Set[String> = new Set[String>(); - - for (var i = 0; i < backends.length; i++) { - var backend = backends[i]; - if (!backend) { - this.addError("Empty backend detected"); - } - else if (!backend.name || backend.name.length == 0) {//name there? - this.addWarning("Every backend setting should have a name."); - backend.name = "backend" + (i + 1); - } - var backendName = "Backend " + backend.name + ":"; - //check for duplicate backends - if (backendNames.has(backend.name)) this.addError("Dublicated backend name: " + backend.name); - backendNames.add(backend.name); - - //check stages - if (!backend.stages || backend.stages.length == 0) { - this.addError(backendName + " The backend setting needs at least one stage"); - continue; - } - - backend.engine = this.resolveEngine(backend.engine); - //check engine and type - if (this.useViperServer(backend) && !ViperServerService.isSupportedType(backend.type)) { - this.addError(backendName + "the backend type " + backend.type + " is not supported, try one of these: " + ViperServerService.supportedTypes); - } - - var stages: Set[String> = new Set[String>(); - var verifyStageFound = false; - for (var i = 0; i < backend.stages.length; i++) { - var stage: Stage = backend.stages[i]; - if (!stage) { - this.addError(backendName + " Empty stage detected"); - } - else if (!stage.name || stage.name.length == 0) { - this.addError(backendName + " Every stage needs a name."); - } else { - var backendAndStage = backendName + " Stage: " + stage.name + ":"; - //check for duplicated stage names - if (stages.has(stage.name)) - this.addError(backendName + " Duplicated stage name: " + stage.name); - stages.add(stage.name); - //check mainMethod - if (!stage.mainMethod || stage.mainMethod.length == 0) - this.addError(backendAndStage + " Missing mainMethod"); - //check customArguments - if (!stage.customArguments) { - this.addError(backendAndStage + " Missing customArguments"); - continue; - } - } - } - for (var i = 0; i < backend.stages.length; i++) { - var stage: Stage = backend.stages[i]; - var BackendMissingStage = backendName + ": Cannot find stage " + stage.name; - if (stage.onParsingError && stage.onParsingError.length > 0 && !stages.has(stage.onParsingError)) - this.addError(BackendMissingStage + "'s onParsingError stage " + stage.onParsingError); - if (stage.onTypeCheckingError && stage.onTypeCheckingError.length > 0 && !stages.has(stage.onTypeCheckingError)) - this.addError(BackendMissingStage + "'s onTypeCheckingError stage " + stage.onTypeCheckingError); - if (stage.onVerificationError && stage.onVerificationError.length > 0 && !stages.has(stage.onVerificationError)) - this.addError(BackendMissingStage + "'s onVerificationError stage " + stage.onVerificationError); - if (stage.onSuccess && stage.onSuccess.length > 0 && !stages.has(stage.onSuccess)) - this.addError(BackendMissingStage + "'s onSuccess stage " + stage.onSuccess); - } - - //check paths - if (!backend.paths || backend.paths.length == 0) { - if (!this.useViperServer(backend)) this.addError(backendName + " The backend setting needs at least one path"); - } else { - if (typeof backend.paths == 'String') { - var temp = backend.paths; - backend.paths = [temp]; - } - for (var i = 0; i < backend.paths.length; i++) { - //extract environment variable or leave unchanged - backend.paths[i] = Settings.checkPath(backend.paths[i], backendName, false, false).path; - } - } - - //check verification timeout - backend.timeout = this.checkTimeout(backend.timeout, "Backend " + backendName + ":"); - } - return null; - } - - private def checkTimeout(timeout: number, prefix: String): number { - if (!timeout || (timeout && timeout <= 0)) { - if (timeout && timeout < 0) { - this.addWarning(prefix + " The timeout of " + timeout + " is interpreted as no timeout."); - } - return null; - } - return timeout; - } - - def backendJars(backend: Backend): String { - var jarFiles = this.getAllJarsInPaths(backend.paths, false); - return this.buildDependencyString(jarFiles); - } - - def viperServerJars(): String { - var jarFiles = this.getAllJarsInPaths(Array[[String]]this.settings.viperServerSettings.serverJars, false); - return this.buildDependencyString(jarFiles); - } - - def buildDependencyString(jarFiles: Array[String]): String { - var dependencies = ""; - var concatenationSymbol = Settings.isWin ? ";" : ":"; - if (jarFiles.length > 0) { - dependencies = dependencies + concatenationSymbol + '"' + jarFiles.join('"' + concatenationSymbol + '"') + '"' - } - return dependencies; - } - - def getAllJarsInPaths(paths: Array[String], recursive: Boolean): Array[String] { - var result: Array[String] = []; - try { - paths.forEach(path => { - if (fs.lstatSync(path).isDirectory()) { - var files = fs.readdirSync(path); - var folders = [] - files.forEach(child => { - child = pathHelper.join(path, child) - if (!fs.lstatSync(child).isDirectory()) { - //child is a file - if (this.isJar(child)) { - //child is a jar file - result.push(child); - } - } else { - folders.push(child); - } - }) - if (recursive) { - result.push(...this.getAllJarsInPaths(folders, recursive)); - } - } else { - if (this.isJar(path)) { - result.push(path) - } - } - }) - } catch (e) { - Log.error("Error getting all Jars in Paths: " + e); - } - return result; - } - - private def isJar(file: String): Boolean { - return file ? file.trim().endsWith(".jar") : false; - } - - private def extractEnvVars(path: String): ResolvedPath { - if (path && path.length > 2) { - while (path.indexOf("%") >= 0) { - var start = path.indexOf("%") - var end = path.indexOf("%", start + 1); - if (end < 0) { - return { path: path, exists: false, error: "unbalanced % in path: " + path }; - } - var envName = path.subString(start + 1, end); - var envValue = process.env[envName]; - if (!envValue) { - return { path: path, exists: false, error: "environment variable " + envName + " used in path " + path + " is not set" }; - } - if (envValue.indexOf("%") >= 0) { - return { path: path, exists: false, error: "environment variable: " + envName + " must not contain %: " + envValue }; - } - path = path.subString(0, start - 1) + envValue + path.subString(end + 1, path.length); - } - } - return { path: path, exists: true }; - } - - private def resolvePath(path: String, executable: Boolean): ResolvedPath { - try { - if (!path) { - return { path: path, exists: false }; - } - path = path.trim(); - - //expand internal variables - var resolvedPath = this.expandViperToolsPath(path); - //handle env Vars - var envVarsExtracted = this.extractEnvVars(resolvedPath); - if (!envVarsExtracted.exists) return envVarsExtracted; - resolvedPath = envVarsExtracted.path; - - //handle files in Path env var - if (resolvedPath.indexOf("/") < 0 && resolvedPath.indexOf("\\") < 0) { - //its only a filename, try to find it in the path - var pathEnvVar: String = process.env.PATH; - if (pathEnvVar) { - var pathList: Array[String] = pathEnvVar.split(Settings.isWin ? ";" : ":"); - for (var i = 0; i < pathList.length; i++) { - var pathElement = pathList[i]; - var combinedPath = this.toAbsolute(pathHelper.join(pathElement, resolvedPath)); - var exists = this.exists(combinedPath, executable); - if (exists.exists) return exists; - } - } - } else { - //handle absolute and relative paths - if (this.home) { - resolvedPath = resolvedPath.replace(/^~($|\/|\\)/, `${this.home}$1`); - } - resolvedPath = this.toAbsolute(resolvedPath); - return this.exists(resolvedPath, executable); - } - return { path: resolvedPath, exists: false }; - } catch (e) { - Log.error("Error resolving path: " + e); - } - } - - private def exists(path: String, executable: Boolean): ResolvedPath { - try { - fs.accessSync(path); - return { path: path, exists: true }; - } catch (e) { } - if (executable && this.isWin && !path.toLowerCase().endsWith(".exe")) { - path += ".exe"; - //only one recursion at most, because the ending is checked - return this.exists(path, executable); - } - return { path: path, exists: false } - } - - private def toAbsolute(path: String): String { - return pathHelper.resolve(pathHelper.normalize(path)); - } - } - -class Version(versionNumbers: Array[Int] = Array(0, 0, 0)) { - private val _key = "VdafSZVOWpe"; - - var versionNumbers: Array[Int] = Array(0, 0, 0); - - def createFromVersion(version: Version): Version = { - try { - if (version != null) { - if (/\d+(\.\d+)+/.test(version)) { - return new Version(version.split(".").map(x => Number.parseInt(x))) - } - } - } catch { - case e: Throwable => Log.debug("Error creating version from Version: " + e); - } - - return new Version(); - } - - def createFromHash(hash) { - try { - if (hash) { - var version = this.decrypt(hash, _key); - //Log.log("hash: " + hash + " decrypted version: " + version, LogLevel.LowLevelDebug); - return this.createFromVersion(version); - } - } catch { - case e: Throwable => Log.debug("Error creating version from hash: " + e); - } - return new Version(); +// +// private def checkPaths(paths: (String | Array[String] | PlatformDependentPath | PlatformDependentListOfPaths), prefix: String): Array[String] = { +// //Log.log("checkPaths(" + JSON.Stringify(paths) + ")", LogLevel.LowLevelDebug); +// var result: Array[String] = [] +// var StringPaths: Array[String] = [] +// if (!paths) { +// this.addError(prefix + " paths are missing"); +// } else if (typeof paths === "String") { +// StringPaths.push(paths) +// } else if (paths instanceof Array) { +// paths.forEach(path => { +// if (typeof path === "String") { +// StringPaths.push(path) +// } +// }) +// } else { +// var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>paths; +// if (Settings.isLinux) { +// return this.checkPaths(platformDependentPath.linux, prefix); +// } else if (Settings.isMac) { +// return this.checkPaths(platformDependentPath.mac, prefix); +// } else if (Settings.isWin) { +// return this.checkPaths(platformDependentPath.windows, prefix); +// } else { +// Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); +// } +// return result; +// } +// +// if (StringPaths.length == 0) { +// this.addError(prefix + ' path has wrong type: expected: String | Array[String] | {windows:(String|Array[String]), mac:(String|Array[String]), linux:(String|Array[String])}, found: ' + typeof paths + " at path: " + JSON.Stringify(paths)); +// } +// +// //resolve the paths +// StringPaths = StringPaths.map(StringPath => { +// var resolvedPath = Settings.resolvePath(StringPath, false); +// if (!resolvedPath.exists) { +// this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); +// } +// return resolvedPath.path +// }); +// if (StringPaths.length == 0) { +// this.addError(prefix + ' no file found at at path: ' + JSON.Stringify(paths)); +// } +// //Log.log("checkPaths result: (" + JSON.Stringify(StringPaths) + ")", LogLevel.LowLevelDebug); +// return StringPaths; +// } +// +// private def checkPath(path: (String | PlatformDependentPath), prefix: String, executable: Boolean, allowPlatformDependentPath: Boolean, allowStringPath: Boolean = true, allowMissingPath = false): ResolvedPath = { +// if (!path) { +// if (!allowMissingPath) this.addError(prefix + " path is missing"); +// return { path: null, exists: false }; +// } +// var StringPath: String; +// if (typeof path === "String") { +// if (!allowStringPath) { +// this.addError(prefix + ' path has wrong type: expected: {windows:String, mac:String, linux:String}, found: ' + typeof path); +// return { path: StringPath, exists: false }; +// } +// StringPath = [String>path; +// } else { +// if (!allowPlatformDependentPath) { +// this.addError(prefix + ' path has wrong type: expected: String, found: ' + typeof path + " at path: " + JSON.Stringify(path)); +// return { path: null, exists: false }; +// } +// var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>path; +// if (Settings.isLinux) { +// StringPath = platformDependentPath.linux; +// } else if (Settings.isMac) { +// StringPath = platformDependentPath.mac; +// } else if (Settings.isWin) { +// StringPath = platformDependentPath.windows; +// } else { +// Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); +// } +// } +// +// if (!StringPath || StringPath.length == 0) { +// if (!allowMissingPath) { +// this.addError(prefix + ' path has wrong type: expected: String' + (executable ? ' or {windows:String, mac:String, linux:String}' : "") + ', found: ' + typeof path + " at path: " + JSON.Stringify(path)); +// } +// return { path: StringPath, exists: false }; +// } +// var resolvedPath = Settings.resolvePath(StringPath, executable); +// if (!resolvedPath.exists) { +// this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); +// } +// return resolvedPath; +// } +// +// private def checkBackends(backends: BackendProperties[]) { +// //Log.log("Checking backends...", LogLevel.Debug); +// if (!backends || backends.length == 0) { +// this.addError("No backend detected, specify at least one backend"); +// return; +// } +// +// var backendNames: Set[String> = new Set[String>(); +// +// for (var i = 0; i < backends.length; i++) { +// var backend = backends[i]; +// if (!backend) { +// this.addError("Empty backend detected"); +// } +// else if (!backend.name || backend.name.length == 0) {//name there? +// this.addWarning("Every backend setting should have a name."); +// backend.name = "backend" + (i + 1); +// } +// var backendName = "Backend " + backend.name + ":"; +// //check for duplicate backends +// if (backendNames.has(backend.name)) this.addError("Dublicated backend name: " + backend.name); +// backendNames.add(backend.name); +// +// //check stages +// if (!backend.stages || backend.stages.length == 0) { +// this.addError(backendName + " The backend setting needs at least one stage"); +// continue; +// } +// +// backend.engine = this.resolveEngine(backend.engine); +// //check engine and type +// if (this.useViperServer(backend) && !ViperServerService.isSupportedType(backend.type)) { +// this.addError(backendName + "the backend type " + backend.type + " is not supported, try one of these: " + ViperServerService.supportedTypes); +// } +// +// var stages: Set[String> = new Set[String>(); +// var verifyStageFound = false; +// for (var i = 0; i < backend.stages.length; i++) { +// var stage: Stage = backend.stages[i]; +// if (!stage) { +// this.addError(backendName + " Empty stage detected"); +// } +// else if (!stage.name || stage.name.length == 0) { +// this.addError(backendName + " Every stage needs a name."); +// } else { +// var backendAndStage = backendName + " Stage: " + stage.name + ":"; +// //check for duplicated stage names +// if (stages.has(stage.name)) +// this.addError(backendName + " Duplicated stage name: " + stage.name); +// stages.add(stage.name); +// //check mainMethod +// if (!stage.mainMethod || stage.mainMethod.length == 0) +// this.addError(backendAndStage + " Missing mainMethod"); +// //check customArguments +// if (!stage.customArguments) { +// this.addError(backendAndStage + " Missing customArguments"); +// continue; +// } +// } +// } +// for (var i = 0; i < backend.stages.length; i++) = { +// var stage: Stage = backend.stages[i]; +// var BackendMissingStage = backendName + ": Cannot find stage " + stage.name; +// if (stage.onParsingError && stage.onParsingError.length > 0 && !stages.has(stage.onParsingError)) +// this.addError(BackendMissingStage + "'s onParsingError stage " + stage.onParsingError); +// if (stage.onTypeCheckingError && stage.onTypeCheckingError.length > 0 && !stages.has(stage.onTypeCheckingError)) +// this.addError(BackendMissingStage + "'s onTypeCheckingError stage " + stage.onTypeCheckingError); +// if (stage.onVerificationError && stage.onVerificationError.length > 0 && !stages.has(stage.onVerificationError)) +// this.addError(BackendMissingStage + "'s onVerificationError stage " + stage.onVerificationError); +// if (stage.onSuccess && stage.onSuccess.length > 0 && !stages.has(stage.onSuccess)) +// this.addError(BackendMissingStage + "'s onSuccess stage " + stage.onSuccess); +// } +// +// //check paths +// if (!backend.paths || backend.paths.length == 0) { +// if (!this.useViperServer(backend)) this.addError(backendName + " The backend setting needs at least one path"); +// } else { +// if (typeof backend.paths == 'String') { +// var temp = backend.paths; +// backend.paths = [temp]; +// } +// for (var i = 0; i < backend.paths.length; i++) { +// //extract environment variable or leave unchanged +// backend.paths[i] = Settings.checkPath(backend.paths[i], backendName, false, false).path; +// } +// } +// +// //check verification timeout +// backend.timeout = this.checkTimeout(backend.timeout, "Backend " + backendName + ":"); +// } +// return null; +// } +// +// private def checkTimeout(timeout: number, prefix: String): number = { +// if (!timeout || (timeout && timeout <= 0)) { +// if (timeout && timeout < 0) { +// this.addWarning(prefix + " The timeout of " + timeout + " is interpreted as no timeout."); +// } +// return null; +// } +// return timeout; +// } +// +// def backendJars(backend: BackendProperties): String = { +// var jarFiles = this.getAllJarsInPaths(backend.paths, false); +// return this.buildDependencyString(jarFiles); +// } +// +// def viperServerJars(): String = { +// var jarFiles = this.getAllJarsInPaths(Array[[String]]this.settings.viperServerSettings.serverJars, false); +// return this.buildDependencyString(jarFiles); +// } +// +// def buildDependencyString(jarFiles: Array[String]): String = { +// var dependencies = ""; +// var concatenationSymbol = Settings.isWin ? ";" : ":"; +// if (jarFiles.length > 0) { +// dependencies = dependencies + concatenationSymbol + '"' + jarFiles.join('"' + concatenationSymbol + '"') + '"' +// } +// return dependencies; +// } +// +// def getAllJarsInPaths(paths: Array[String], recursive: Boolean): Array[String] = { +// var result: Array[String] = []; +// try { +// paths.forEach(path => { +// if (fs.lstatSync(path).isDirectory()) { +// var files = fs.readdirSync(path); +// var folders = [] +// files.forEach(child => { +// child = pathHelper.join(path, child) +// if (!fs.lstatSync(child).isDirectory()) { +// //child is a file +// if (this.isJar(child)) { +// //child is a jar file +// result.push(child); +// } +// } else { +// folders.push(child); +// } +// }) +// if (recursive) { +// result.push(...this.getAllJarsInPaths(folders, recursive)); +// } +// } else { +// if (this.isJar(path)) { +// result.push(path) +// } +// } +// }) +// } catch (e) { +// Log.error("Error getting all Jars in Paths: " + e); +// } +// return result; +// } +// +// private def isJar(file: String): Boolean = { +// return file ? file.trim().endsWith(".jar") : false; +// } +// +// private def extractEnvVars(path: String): ResolvedPath = { +// if (path && path.length > 2) { +// while (path.indexOf("%") >= 0) { +// var start = path.indexOf("%") +// var end = path.indexOf("%", start + 1); +// if (end < 0) { +// return { path: path, exists: false, error: "unbalanced % in path: " + path }; +// } +// var envName = path.subString(start + 1, end); +// var envValue = process.env[envName]; +// if (!envValue) { +// return { path: path, exists: false, error: "environment variable " + envName + " used in path " + path + " is not set" }; +// } +// if (envValue.indexOf("%") >= 0) { +// return { path: path, exists: false, error: "environment variable: " + envName + " must not contain %: " + envValue }; +// } +// path = path.subString(0, start - 1) + envValue + path.subString(end + 1, path.length); +// } +// } +// return { path: path, exists: true }; +// } +// +// private def resolvePath(path: String, executable: Boolean): ResolvedPath = { +// try { +// if (!path) { +// return { path: path, exists: false }; +// } +// path = path.trim(); +// +// //expand internal variables +// var resolvedPath = this.expandViperToolsPath(path); +// //handle env Vars +// var envVarsExtracted = this.extractEnvVars(resolvedPath); +// if (!envVarsExtracted.exists) return envVarsExtracted; +// resolvedPath = envVarsExtracted.path; +// +// //handle files in Path env var +// if (resolvedPath.indexOf("/") < 0 && resolvedPath.indexOf("\\") < 0) { +// //its only a filename, try to find it in the path +// var pathEnvVar: String = process.env.PATH; +// if (pathEnvVar) { +// var pathList: Array[String] = pathEnvVar.split(Settings.isWin ? ";" : ":"); +// for (var i = 0; i < pathList.length; i++) { +// var pathElement = pathList[i]; +// var combinedPath = this.toAbsolute(pathHelper.join(pathElement, resolvedPath)); +// var exists = this.exists(combinedPath, executable); +// if (exists.exists) return exists; +// } +// } +// } else { +// //handle absolute and relative paths +// if (this.home) { +// resolvedPath = resolvedPath.replace(/^~($|\/|\\)/, `${this.home}$1`); +// } +// resolvedPath = this.toAbsolute(resolvedPath); +// return this.exists(resolvedPath, executable); +// } +// return { path: resolvedPath, exists: false }; +// } catch (e) { +// Log.error("Error resolving path: " + e); +// } +// } +// +// private def exists(path: String, executable: Boolean): ResolvedPath = { +// try { +// fs.accessSync(path); +// return { path: path, exists: true }; +// } catch (e) { } +// if (executable && this.isWin && !path.toLowerCase().endsWith(".exe")) { +// path += ".exe"; +// //only one recursion at most, because the ending is checked +// return this.exists(path, executable); +// } +// return { path: path, exists: false } +// } +// +// private def toAbsolute(path: String): String = { +// return pathHelper.resolve(pathHelper.normalize(path)); +// } } - - private def encrypt(msg: String, key: String): String { - var res: String = "" - var parity: number = 0; - for (var i = 0; i < msg.length; i++) { - var keyChar: number = key.charCodeAt(i % key.length); - //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); - var char: number = msg.charCodeAt(i); - //Log.log("char " + msg.charAt(i) + " charCode: " + char,LogLevel.LowLevelDebug); - var cypher: number = (char ^ keyChar) - parity = (parity + cypher % (16 * 16)) % (16 * 16); - //Log.log("cypher " + (char ^ keyChar).toString() + " hex: "+ cypher,LogLevel.LowLevelDebug); - res += this.pad(cypher); - } - return res + this.pad(parity); - } - - private def pad(n: number): String { - var s = n.toString(16); - return (s.length == 1 ? "0" : "") + s; - } - - private def decrypt(cypher: String, key: String): String { - //Log.log("decrypt",LogLevel.LowLevelDebug); - var res: String = "" - var parity: number = 0; - if (!cypher || cypher.length < 2 || cypher.length % 2 != 0) { - return ""; - } - for (var i = 0; i < cypher.length - 2; i += 2) { - var keyChar: number = key.charCodeAt((i / 2) % key.length); - //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); - var char: number = (16 * parseInt(cypher.charAt(i), 16)) + parseInt(cypher.charAt(i + 1), 16) - parity = (parity + char % (16 * 16)) % (16 * 16); - //Log.log("char " + char,LogLevel.LowLevelDebug); - //Log.log("encChar " + String.fromCharCode(char ^ keyChar) + " charCode: "+(char ^ keyChar),LogLevel.LowLevelDebug); - res += String.fromCharCode(char ^ keyChar) - } - if (parity != (16 * parseInt(cypher.charAt(cypher.length - 2), 16)) + parseInt(cypher.charAt(cypher.length - 1), 16)) { - return "" - } else { - return res - } - } - - toString(): String { - return this.versionNumbers.join("."); - } - - def testhash() { - var s = "1.0.0"; - var en = this.encrypt(s, Version.Key); - var de = this.decrypt(en, Version.Key); - Log.log("Hash Test: " + s + " -> " + en + " -> " + de, LogLevel.LowLevelDebug) - } - - def hash(version: String): String { - var hash = this.encrypt(version, Version.Key); - //Log.log("version: " + version + " hash: " + hash, LogLevel.LowLevelDebug); - return hash; - } - - //1: this is larger, -1 other is larger - compare(other: Version): number { - for (var i = 0; i < this.versionNumbers.length; i++) { - if (i >= other.versionNumbers.length) return 1; - if (this.versionNumbers[i] > other.versionNumbers[i]) return 1; - if (this.versionNumbers[i] < other.versionNumbers[i]) return -1; - } - return this.versionNumbers.length < other.versionNumbers.length ? -1 : 0; - } -} \ No newline at end of file +// +//class Version(versionNumbers: Array[Int] = Array(0, 0, 0)) { +// private val _key = "VdafSZVOWpe"; +// +// var versionNumbers: Array[Int] = Array(0, 0, 0); +// +// def createFromVersion(version: Version): Version = { +// try { +// if (version != null) { +// if (/\d+(\.\d+)+/.test(version)) { +// return new Version(version.split(".").map(x => Number.parseInt(x))) +// } +// } +// } catch { +// case e: Throwable => Log.debug("Error creating version from Version: " + e); +// } +// +// return new Version(); +// } +// +// def createFromHash(hash) = { +// try { +// if (hash) { +// var version = this.decrypt(hash, _key); +// //Log.log("hash: " + hash + " decrypted version: " + version, LogLevel.LowLevelDebug); +// return this.createFromVersion(version); +// } +// } catch { +// case e: Throwable => Log.debug("Error creating version from hash: " + e); +// } +// return new Version(); +// } +// +// private def encrypt(msg: String, key: String): String = { +// var res: String = "" +// var parity: number = 0; +// for (var i = 0; i < msg.length; i++) { +// var keyChar: number = key.charCodeAt(i % key.length); +// //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); +// var char: number = msg.charCodeAt(i); +// //Log.log("char " + msg.charAt(i) + " charCode: " + char,LogLevel.LowLevelDebug); +// var cypher: number = (char ^ keyChar) +// parity = (parity + cypher % (16 * 16)) % (16 * 16); +// //Log.log("cypher " + (char ^ keyChar).toString() + " hex: "+ cypher,LogLevel.LowLevelDebug); +// res += this.pad(cypher); +// } +// return res + this.pad(parity); +// } +// +// private def pad(n: number): String = { +// var s = n.toString(16); +// return (s.length == 1 ? "0" : "") + s; +// } +// +// private def decrypt(cypher: String, key: String): String = { +// //Log.log("decrypt",LogLevel.LowLevelDebug); +// var res: String = "" +// var parity: number = 0; +// if (!cypher || cypher.length < 2 || cypher.length % 2 != 0) { +// return ""; +// } +// for (var i = 0; i < cypher.length - 2; i += 2) { +// var keyChar: number = key.charCodeAt((i / 2) % key.length); +// //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); +// var char: number = (16 * parseInt(cypher.charAt(i), 16)) + parseInt(cypher.charAt(i + 1), 16) +// parity = (parity + char % (16 * 16)) % (16 * 16); +// //Log.log("char " + char,LogLevel.LowLevelDebug); +// //Log.log("encChar " + String.fromCharCode(char ^ keyChar) + " charCode: "+(char ^ keyChar),LogLevel.LowLevelDebug); +// res += String.fromCharCode(char ^ keyChar) +// } +// if (parity != (16 * parseInt(cypher.charAt(cypher.length - 2), 16)) + parseInt(cypher.charAt(cypher.length - 1), 16)) { +// return "" +// } else { +// return res +// } +// } +// +// toString(): String { +// return this.versionNumbers.join("."); +// } +// +// def testhash() { +// var s = "1.0.0"; +// var en = this.encrypt(s, Version.Key); +// var de = this.decrypt(en, Version.Key); +// Log.log("Hash Test: " + s + " -> " + en + " -> " + de, LogLevel.LowLevelDebug) +// } +// +// def hash(version: String): String = { +// var hash = this.encrypt(version, Version.Key); +// //Log.log("version: " + version + " hash: " + hash, LogLevel.LowLevelDebug); +// return hash; +// } +// +// //1: this is larger, -1 other is larger +// def compare(other: Version): Int = { +// for (var i = 0; i < this.versionNumbers.length; i++) { +// if (i >= other.versionNumbers.length) return 1; +// if (this.versionNumbers[i] > other.versionNumbers[i]) return 1; +// if (this.versionNumbers[i] < other.versionNumbers[i]) return -1; +// } +// return this.versionNumbers.length < other.versionNumbers.length ? -1 : 0; +// } +//} \ No newline at end of file diff --git a/src/main/scala/viper/server/ViperHttpServer.scala b/src/main/scala/viper/server/ViperHttpServer.scala index 9814841..e36300b 100644 --- a/src/main/scala/viper/server/ViperHttpServer.scala +++ b/src/main/scala/viper/server/ViperHttpServer.scala @@ -8,29 +8,28 @@ package viper.server -import scala.concurrent.Future -import scala.concurrent.duration._ -import scala.util.{Failure, Success} import akka.NotUsed -import akka.pattern.ask -import akka.util.Timeout import akka.actor.PoisonPill -import akka.stream.scaladsl.Source import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Route +import akka.pattern.ask +import akka.stream.scaladsl.Source +import akka.util.Timeout import edu.mit.csail.sdg.alloy4.A4Reporter import edu.mit.csail.sdg.parser.CompUtil import edu.mit.csail.sdg.translator.{A4Options, TranslateAlloyToKodkod} -import viper.server.protocol.ViperServerProtocol._ -import viper.server.protocol.ViperIDEProtocol._ -import viper.silver.reporter._ -import viper.silver.logger.ViperLogger -import ViperRequests.{AlloyGenerationRequest, CacheResetRequest, VerificationRequest} +import viper.server.ViperRequests.{AlloyGenerationRequest, CacheResetRequest, VerificationRequest} import viper.server.core.{VerificationJobHandler, ViperCache, ViperCoreServer} +import viper.server.protocol.ViperIDEProtocol._ import viper.server.protocol.ViperServerProtocol +import viper.server.protocol.ViperServerProtocol._ import viper.server.utility.AstGenerator +import viper.silver.logger.ViperLogger +import viper.silver.reporter._ -import scala.util.Try +import scala.concurrent.Future +import scala.concurrent.duration._ +import scala.util.{Failure, Success, Try} class ViperHttpServer(private val _args: Array[String]) extends ViperCoreServer(_args) { diff --git a/src/main/scala/viper/server/ViperServerService.scala b/src/main/scala/viper/server/ViperServerService.scala index 22dec7e..58d0160 100644 --- a/src/main/scala/viper/server/ViperServerService.scala +++ b/src/main/scala/viper/server/ViperServerService.scala @@ -2,39 +2,29 @@ package viper.server import java.util.concurrent.{CompletableFuture => CFuture} -import akka.actor.{Actor, PoisonPill, Props} - -import scala.compat.java8.FutureConverters._ +import akka.actor.{PoisonPill, Props} import akka.pattern.ask import akka.util.Timeout - -import scala.concurrent.Future -import scala.concurrent.duration._ -import viper.server.core.{VerificationJobHandler, ViperCoreServer} -import viper.server.protocol.ViperServerProtocol.Stop import viper.server.VerificationState._ -import viper.server.VerificationSuccess._ import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} +import viper.server.core.{VerificationJobHandler, ViperCoreServer} +import viper.server.protocol.ViperServerProtocol.Stop import viper.server.utility.AstGenerator -import viper.silver.ast.{Method, Program} -import viper.silver.reporter.{Message, ProgramOutlineReport} +import viper.silver.ast.Program + +import scala.compat.java8.FutureConverters._ +import scala.concurrent.Future +import scala.concurrent.duration._ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { - var instanceCount: Int = 0 - var isSessionRunning: Boolean = false private var _ready: Boolean = false protected var timeout: Int = _ - private var _server_logfile: String = _ + def isReady: Boolean = _ready - // the JID that ViperServer assigned to the current verification job. - private var _job_id: Int = _ - - def isReady: Boolean = _ready; - - def setReady(backend: Backend): Unit = { + def setReady(backend: BackendProperties): Unit = { _ready = true Coordinator.backend = backend Coordinator.startingOrRestarting = false @@ -42,75 +32,22 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { Coordinator.client.notifyBackendReady(S2C_Commands.BackendReady) } - def startStageProcess( - stage: Stage, fileToVerify: String, - onData: String => Unit, - onError: String => Unit, - onClose: Int => Unit): Unit = { - - try { - Log.lowLevel("Start Stage Process") - - isSessionRunning = true - val command = getStageCommand(fileToVerify, stage); - startVerifyStream(command) - } catch { - case e: Throwable => Log.debug("Error starting stage process: " + e); - } - } - - def getStageCommand(fileToVerify: String, stage: Stage): String = { - val args: String = getViperBackendClassName(stage) + " " + stage.customArguments - val command = Settings.expandCustomArguments(args, stage, fileToVerify, Coordinator.backend) - Log.debug(command) - command - } - - def getViperBackendClassName(stage: Stage): String = { - Coordinator.backend.backend_type match { - case "silicon" => "silicon" - case "carbon" => "carbon" - case "other" => stage.mainMethod - case _ => throw new Error(s"Invalid verification backend value. " + - s"Possible values are [silicon | carbon | other] " + - s"but found ${Coordinator.backend}") - } - } - - def swapBackend(newBackend: Backend): Unit = { + def swapBackend(newBackend: BackendProperties): Unit = { setReady(newBackend) } - def stopVerification(): CFuture[Boolean] = { - lookupJob(_job_id) match { - case Some(handle_future) => - handle_future.flatMap(handle => { - implicit val askTimeout: Timeout = Timeout(config.actorCommunicationTimeout() milliseconds) - val interrupt: Future[String] = (handle.controller_actor ? Stop(true)).mapTo[String] - handle.controller_actor ! PoisonPill // the actor played its part. - interrupt - }).toJava.toCompletableFuture.thenApply(msg => { - Log.info(msg); - true - }) - case _ => - // Did not find a job with this jid. - CFuture.failedFuture(new Throwable(s"The verification job #$_job_id does not exist.")) - } - } - def setStopping(): Unit = { Log.debug("Set Stopping... ") _ready = false Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopping), None) + Coordinator.sendStateChangeNotification(StateChangeParams(Stopping, false, false), None) } def setStopped(): Unit = { Log.debug("Set Stopped. ") _ready = false Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopped), None) + Coordinator.sendStateChangeNotification(StateChangeParams(Stopped, false, false), None) } private def getArgListFromArgString(arg_str: String): List[String] = { @@ -122,7 +59,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } } - def startVerify(command: String): Int = { + def startVerification(command: String): Int = { Log.debug("Requesting ViperServer to start new job...") // Todo start verification in VCS @@ -131,7 +68,6 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { val arg_list_partial = arg_list.dropRight(1) // Parse file - val astGen = new AstGenerator(logger) var ast_option: Option[Program] = None try { @@ -139,9 +75,12 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } catch { case _: java.nio.file.NoSuchFileException => Log.debug("The file for which verification has been requested was not found.") - return + return-1 } - val ast = ast_option.getOrElse(return Log.debug("The file for which verification has been requested contained syntax errors.")) + val ast = ast_option.getOrElse({ + Log.debug("The file for which verification has been requested contained syntax errors.") + return -1 + }) // prepare backend config val backend = arg_list_partial match { @@ -162,109 +101,91 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { jid.id } - // This method should start a verification and, after getting back the corresponding jobId, should - // immmediately ask for messages. - private def startVerifyStream(command: String): Unit = { + def startStreaming(jid: Int, relayActor_props: Props): Unit = { Log.debug("Sending verification request to ViperServer...") - _job_id = startVerify(command) - val relay_actor = system.actorOf(RelayActor.props()) - val termination_status = streamMessages(_job_id, relay_actor) - } - - private object RelayActor { - case object Result - def props(): Props = Props(new RelayActor()) + val relay_actor = system.actorOf(relayActor_props) + streamMessages(jid, relay_actor) } - def flushCache(filePath?: String): CFuture[String] = { - return new Promise((resolve, reject) => { - val url = this._url + ':' + this._port + '/cache/flush' - if (filePath) { - Log.log(`Requesting ViperServer to flush the cache for (` + filePath + `)...`, LogLevel.Info) - - val options = { - url: url, - headers: {'content-type': 'application/json'}, - body: JSON.stringify({ backend: Coordinator.backend.name, file: filePath }) - } - - request.post(options).on('error', (error) => { - Log.log(`error while requesting ViperServer to flush the cache for (` + filePath + `).` + - ` Request URL: ${url}\n` + - ` Error message: ${error}`, LogLevel.Default) - reject(error) - - }).on('data', (data) => { - val response = JSON.parse(data.toString()) - if ( !response.msg ) { - Log.log(`ViperServer did not complain about the way we requested it to flush the cache for (` + filePath + `).` + - ` However, it also did not provide the expected bye-bye message.` + - ` It said: ${data.toString}`, LogLevel.Debug) - resolve(response) - } else { - Log.log(`ViperServer has confirmed that the cache for (` + filePath + `) has been flushed.`, LogLevel.Debug) - resolve(response.msg) - } - }) - - } else { - Log.log(`Requesting ViperServer to flush the entire cache...`, LogLevel.Info) - - request.get(url).on('error', (error) => { - Log.log(`error while requesting ViperServer to flush the entire cache.` + - ` Request URL: ${url}\n` + - ` Error message: ${error}`, LogLevel.Default) - reject(error) - - }).on('data', (data) => { - val response = JSON.parse(data.toString()) - if ( !response.msg ) { - Log.log(`ViperServer did not complain about the way we requested it to flush the entire cache.` + - ` However, it also did not provide the expected bye-bye message.` + - ` It said: ${data.toString}`, LogLevel.Debug) - resolve(response) - } else { - Log.log(`ViperServer has confirmed that the entire cache has been flushed.`, LogLevel.Debug) - resolve(response.msg) - } - }) - } - }) - } - - private def sendStopRequest(): CFuture[Boolean] = { - return new Promise((resolve, reject) => { - Log.log(`Requesting ViperServer to exit...`, LogLevel.Debug) - val url = this._url + ':' + this._port + '/exit' - request.get(url).on('error', (err) => { - Log.log(`error while requesting ViperServer to stop.` + - ` Request URL: ${url}\n` + - ` Error message: ${err}`, LogLevel.Default) - reject(err) - }).on('data', (data) => { - val response = JSON.parse(data.toString()) - if ( !response.msg ) { - Log.log(`ViperServer did not complain about the way we requested it to exit.` + - ` However, it also did not provide the expected bye-bye message.` + - ` It said: ${data.toString}`, LogLevel.Debug) - resolve(true) - } else if ( response.msg !== 'shutting down...' ) { - Log.log(`ViperServer responded with an unexpected bye-bye message: ${response.msg}`, - LogLevel.Debug) - resolve(true) - } else { - Log.log(`ViperServer has exited properly.`, LogLevel.Debug) - resolve(true) - } - }) - }) + def stopVerification(jid: Int): CFuture[Boolean] = { + lookupJob(jid) match { + case Some(handle_future) => + handle_future.flatMap(handle => { + implicit val askTimeout: Timeout = Timeout(config.actorCommunicationTimeout() milliseconds) + val interrupt: Future[String] = (handle.controller_actor ? Stop(true)).mapTo[String] + handle.controller_actor ! PoisonPill // the actor played its part. + interrupt + }).toJava.toCompletableFuture.thenApply(msg => { + Log.info(msg) + true + }) + case _ => + // Did not find a job with this jid. + CFuture.failedFuture(new Throwable(s"The verification job #$jid does not exist.")) + } } - def isSupportedType(t: String) = { - if (!t) { +// def flushCache(filePath?: String): CFuture[String] = { +// return new Promise((resolve, reject) => { +// val url = this._url + ':' + this._port + '/cache/flush' +// if (filePath) { +// Log.log(`Requesting ViperServer to flush the cache for (` + filePath + `)...`, LogLevel.Info) +// +// val options = { +// url: url, +// headers: {'content-type': 'application/json'}, +// body: JSON.stringify({ backend: Coordinator.backend.name, file: filePath }) +// } +// +// request.post(options).on('error', (error) => { +// Log.log(`error while requesting ViperServer to flush the cache for (` + filePath + `).` + +// ` Request URL: ${url}\n` + +// ` Error message: ${error}`, LogLevel.Default) +// reject(error) +// +// }).on('data', (data) => { +// val response = JSON.parse(data.toString()) +// if ( !response.msg ) { +// Log.log(`ViperServer did not complain about the way we requested it to flush the cache for (` + filePath + `).` + +// ` However, it also did not provide the expected bye-bye message.` + +// ` It said: ${data.toString}`, LogLevel.Debug) +// resolve(response) +// } else { +// Log.log(`ViperServer has confirmed that the cache for (` + filePath + `) has been flushed.`, LogLevel.Debug) +// resolve(response.msg) +// } +// }) +// +// } else { +// Log.log(`Requesting ViperServer to flush the entire cache...`, LogLevel.Info) +// +// request.get(url).on('error', (error) => { +// Log.log(`error while requesting ViperServer to flush the entire cache.` + +// ` Request URL: ${url}\n` + +// ` Error message: ${error}`, LogLevel.Default) +// reject(error) +// +// }).on('data', (data) => { +// val response = JSON.parse(data.toString()) +// if ( !response.msg ) { +// Log.log(`ViperServer did not complain about the way we requested it to flush the entire cache.` + +// ` However, it also did not provide the expected bye-bye message.` + +// ` It said: ${data.toString}`, LogLevel.Debug) +// resolve(response) +// } else { +// Log.log(`ViperServer has confirmed that the entire cache has been flushed.`, LogLevel.Debug) +// resolve(response.msg) +// } +// }) +// } +// }) +// } + + def isSupportedType(t: String): Boolean = { + if (t == null) { return false } - return t.toLowerCase() == 'carbon' || t.toLowerCase() == 'silicon' || t.toLowerCase() == 'other' + t.toLowerCase() == "carbon" || t.toLowerCase() == "silicon" || t.toLowerCase() == "other" } def supportedTypes(): String = { diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 7db57eb..4f1f9dd 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -11,10 +11,10 @@ import akka.stream.scaladsl.{Keep, Sink, Source, SourceQueueWithComplete} import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.util.Timeout import org.reactivestreams.Publisher -import viper.server.protocol.ViperServerProtocol._ +import viper.server.ViperConfig import viper.server.core.ViperBackendConfigs._ +import viper.server.protocol.ViperServerProtocol._ import viper.server.protocol.{ReporterProtocol, ViperServerProtocol} -import viper.server.ViperConfig import viper.silver.ast import viper.silver.logger.ViperLogger import viper.silver.reporter.Message diff --git a/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala b/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala index d0bd395..80b24d8 100644 --- a/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala @@ -6,7 +6,6 @@ import akka.stream.scaladsl.Flow import akka.util.ByteString import edu.mit.csail.sdg.translator.A4Solution import spray.json.DefaultJsonProtocol -import viper.server.core.ViperBackend import viper.server.writer.{AlloySolutionWriter, SymbExLogReportWriter, TermWriter} import viper.silicon.SymbLog import viper.silicon.state.terms.Term diff --git a/src/main/scala/viper/server/writer/AlloySolutionWriter.scala b/src/main/scala/viper/server/writer/AlloySolutionWriter.scala index 76d3570..1aefb35 100644 --- a/src/main/scala/viper/server/writer/AlloySolutionWriter.scala +++ b/src/main/scala/viper/server/writer/AlloySolutionWriter.scala @@ -5,8 +5,8 @@ import edu.mit.csail.sdg.ast.{ExprVar, Sig} import edu.mit.csail.sdg.translator.{A4Solution, A4Tuple} import spray.json.{JsArray, JsObject, JsString, JsValue} +import scala.collection.JavaConversions._ import scala.collection.mutable.ListBuffer -import collection.JavaConversions._ object AlloySolutionWriter { From a3704c6d086aec9dbce2686f8d34fa0632b148f0 Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 12 Oct 2020 00:09:20 +0200 Subject: [PATCH 04/79] verification running, diagnostics showing --- .vscode/settings.json | 3 + src/main/scala/viper/server/Common.scala | 31 ++++--- src/main/scala/viper/server/Coordinator.scala | 14 ++- .../scala/viper/server/DataProtocol.scala | 16 +++- src/main/scala/viper/server/FileManager.scala | 87 ++++++++++-------- .../viper/server/IdeLanguageClient.scala | 33 +------ src/main/scala/viper/server/IdeLog.scala | 3 +- .../viper/server/LanguageServerReceiver.scala | 91 ++++++++++++++----- src/main/scala/viper/server/Settings.scala | 84 ++++++++--------- src/main/scala/viper/server/ViperServer.scala | 2 +- .../viper/server/ViperServerService.scala | 28 +++--- 11 files changed, 224 insertions(+), 168 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f9a5742 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.fontSize": 12 +} \ No newline at end of file diff --git a/src/main/scala/viper/server/Common.scala b/src/main/scala/viper/server/Common.scala index 6c84890..e96e093 100644 --- a/src/main/scala/viper/server/Common.scala +++ b/src/main/scala/viper/server/Common.scala @@ -1,31 +1,33 @@ package viper.server -import java.nio.file.Paths +import java.net.URI +import java.nio.file.{Path, Paths} import java.util.concurrent.CompletableFuture import org.eclipse.lsp4j.Position object Common { - var viperFileEndings: Array[String] = _ + //TODO Get this from client programatically + var viperFileEndings: Array[String] = Array("vpr", ".sil") - def uriToPath(uri: String): String = { - Paths.get(uri).toString + def uriFromString(uri: String): URI = { + URI.create(uri) } - def pathToUri(path: String): String = { - Paths.get(path).toUri.toString + def uriToPath(uri: URI): Path = { + Path.of(uri) } - def filenameFromPath(path: String): String = { - //Todo - "TODO" +// def pathToUri(path: String): String = { +// Paths.get(path).toUri.toString +// } + + def filenameFromUri(uri: String): String = { + Paths.get(uri).getFileName.toString } def refreshEndings(): CompletableFuture[Void] = { - def f(endings: Array[String]): Unit = { - viperFileEndings = endings - } - Coordinator.client.requestVprFileEndings().thenAccept((s:Array[String]) => { + Coordinator.client.requestVprFileEndings().thenAccept((s: Array[String]) => { viperFileEndings = s }).exceptionally(e => { Log.debug(s"GetViperFileEndings request was rejected by the client: $e") @@ -42,8 +44,11 @@ object Common { Log.debug("Refreshing the viper file endings.") refreshEndings().thenApply(a => { if (areEndingsDefined) { + println("Endings are defined!") + viperFileEndings.foreach(s => s.drop(1)) viperFileEndings.exists(ending => uri.endsWith(ending)) } else { + println("Endings not are defined!") false } }) diff --git a/src/main/scala/viper/server/Coordinator.scala b/src/main/scala/viper/server/Coordinator.scala index 78d7c33..d93df6d 100644 --- a/src/main/scala/viper/server/Coordinator.scala +++ b/src/main/scala/viper/server/Coordinator.scala @@ -20,7 +20,7 @@ object Coordinator { var startingOrRestarting: Boolean = false var backend: BackendProperties = _ - val verifier: ViperServerService = new ViperServerService(Array()) + var verifier: ViperServerService = _ def getAddress: String = url + ":" + port @@ -49,8 +49,8 @@ object Coordinator { } def stopAllRunningVerifications(): CompletableFuture[Void] = { - if (Coordinator.files.nonEmpty) { - val promises = Coordinator.files.values.map(task => task.abortVerification()).toSeq + if (files.nonEmpty) { + val promises = files.values.map(task => task.abortVerification()).toSeq CompletableFuture.allOf(promises:_*) } else { CompletableFuture.completedFuture(null) @@ -59,8 +59,12 @@ object Coordinator { //Communication requests and notifications sent to language client def sendStateChangeNotification(params: StateChangeParams, task: Option[FileManager]): Unit = { - if (task.isDefined) task.get.state = params.newState - client.notifyStateChanged(params) + if (task.isDefined) task.get.state = VerificationState.apply(params.newState.toInt) + try { + client.notifyStateChanged(params) + } catch { + case e: Throwable => println(e) + } } // def sendStepsAsDecorationOptions(decorations: StepsAsDecorationOptionsResult) = { diff --git a/src/main/scala/viper/server/DataProtocol.scala b/src/main/scala/viper/server/DataProtocol.scala index daa143c..cc9ce07 100644 --- a/src/main/scala/viper/server/DataProtocol.scala +++ b/src/main/scala/viper/server/DataProtocol.scala @@ -54,15 +54,15 @@ case class Hint(msg: String, showButton1: Boolean, showButton2: Boolean) //Might need to change assignments case class StateChangeParams( - newState: VerificationState, + newState: Double, manuallyTriggered: Boolean, //should be Boolean verificationCompleted: Boolean, //should be Boolean progress: Double = -1, - success: VerificationSuccess = null, + success: Double = NA.id, filename: String = null, backendName: String = null, - time: Int = -1, - nofErrors: Int = -1, + time: Double = -1, + nofErrors: Double = -1, // verificationNeeded: Double = Double.NaN, //should be Boolean uri: String = null, stage: String = null, @@ -170,6 +170,7 @@ case class AdvancedFeatureSettings( version: String) extends VersionedSettings +// scope == null means global scope case class Definition(definition_type: String, name: String, code_location: Position, scope: Range) object BackendOutputType { @@ -197,4 +198,9 @@ case class BackendOutput( file: String = null, errors: Array[Error] = null, //for Outlin // members: Array[Member] = null, //for Definitions - definitions: Array[Definition] = null) \ No newline at end of file + definitions: Array[Definition] = null) + +case class BackendReadyParams ( + name: String, //name of the backend ready to use + restarted: Boolean, //should the open file be reverified + isViperServer: Boolean) diff --git a/src/main/scala/viper/server/FileManager.scala b/src/main/scala/viper/server/FileManager.scala index 7b8e59a..9e3d0fa 100644 --- a/src/main/scala/viper/server/FileManager.scala +++ b/src/main/scala/viper/server/FileManager.scala @@ -1,5 +1,7 @@ package viper.server +import java.net.URI +import java.nio.file.{Path, Paths} import java.util.concurrent.{CompletableFuture => CFuture} import akka.actor.{Actor, Props} @@ -12,10 +14,11 @@ import viper.silver.reporter._ import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer -class FileManager(fileUri: String) { +class FileManager(file_uri: String) { // file under verification - var path: String = _ - var filename: String = _ + var uri: URI = URI.create(file_uri) + var path: Path = Paths.get(uri) + var filename: String = path.getFileName.toString var lastSuccess: VerificationSuccess = NA var internalErrorMessage: String = "" @@ -36,7 +39,7 @@ class FileManager(fileUri: String) { var backendType: String = _ var progress: Progress = _ // var shownExecutionTrace: Array[ExecutionTrace] = _ - var symbolInformation: ArrayBuffer[SymbolInformation] = _ + var symbolInformation: ArrayBuffer[SymbolInformation] = ArrayBuffer() var definitions: ArrayBuffer[Definition] = _ //working variables @@ -47,7 +50,7 @@ class FileManager(fileUri: String) { def resetDiagnostics(): Unit = { diagnostics = ArrayBuffer() val diagnosticParams = new PublishDiagnosticsParams() - diagnosticParams.setUri(fileUri) + diagnosticParams.setUri(file_uri) diagnosticParams.setDiagnostics(diagnostics.asJava) Coordinator.client.publishDiagnostics(diagnosticParams) } @@ -89,10 +92,12 @@ class FileManager(fileUri: String) { }) } - def startStageProcess(stage: Stage, fileToVerify: String): Option[String] = { + def startStageProcess(fileToVerify: String): Option[String] = { +// def startStageProcess(stage: Stage, fileToVerify: String): Option[String] = { try { Log.lowLevel("Start Stage Process") - Some(getStageCommand(fileToVerify, stage)) +// Some(getStageCommand(fileToVerify, stage)) + Some("silicon " + fileToVerify) } catch { case e: Throwable => Log.debug("Error starting stage process: " + e) @@ -101,9 +106,9 @@ class FileManager(fileUri: String) { } def getStageCommand(fileToVerify: String, stage: Stage): String = { -// val args: String = getViperBackendClassName(stage) + " " + stage.customArguments + val args: String = getViperBackendClassName(stage) + " " + stage.customArguments // val command = Settings.expandCustomArguments(args, stage, fileToVerify, Coordinator.backend) - val command = "silicon --cachingDisabled" + val command = "" Log.debug(command) command } @@ -123,28 +128,28 @@ class FileManager(fileUri: String) { prepareVerification() this.manuallyTriggered = manuallyTriggered - // This should have exactly one stage - val stage = Coordinator.backend.stages.head - if (stage == null) { -// Log.debug("backend " + Coordinator.backend.name + " has no " + Settings.VERIFY + " stage, even though the settigns were checked.") - Log.debug("Should have exactly one stage") - return false - } - - // why must this be variable? - path = Common.uriToPath(fileUri) - filename = Common.filenameFromPath(path) - Log.toLogFile("verify " + filename) + //TODO this should work with precisely one stage + //This should have exactly one stage +// if (Coordinator.backend.stages == null || Coordinator.backend.stages.head == null) { +//// Log.debug("backend " + Coordinator.backend.name + " has no " + Settings.VERIFY + " stage, even though the settigns were checked.") +// println("no stage found!") +// Log.debug("Should have exactly one stage") +// return false +// } +// val stage = Coordinator.backend.stages.head - Coordinator.executedStages.append(stage) +// Coordinator.executedStages.append(stage) + Log.info("verify " + filename) Log.info(Coordinator.backend.name + " verification started") - val params = StateChangeParams(VerificationRunning, manuallyTriggered, false, filename = filename) + val params = StateChangeParams(VerificationRunning.id, manuallyTriggered, false, filename = filename) Coordinator.sendStateChangeNotification(params, Some(this)) - val command_opt = startStageProcess(stage, path) - val command = command_opt.getOrElse(return false) +// val command = startStageProcess(stage, path).getOrElse(return false) + val command = startStageProcess(path.toString).getOrElse(return false) + println(s"Successfully generated command: $command") jid = Coordinator.verifier.startVerification(command) + println(s"Successfully started verifying with id: $jid") Coordinator.verifier.startStreaming(jid, RelayActor.props(this)) true } @@ -159,7 +164,13 @@ class FileManager(fileUri: String) { case ProgramOutlineReport(members) => symbolInformation = ArrayBuffer() members.foreach(m => { - val location: Location = new Location(fileUri, null) + val member_start = m.pos.asInstanceOf[SourcePosition].start + val member_end = m.pos.asInstanceOf[SourcePosition].end.getOrElse(member_start) + val range_start = new Position(member_start.line, member_start.column) + val range_end = new Position(member_end.line, member_end.column) + val range = new Range(range_start, range_end) + val location: Location = new Location(file_uri, range) + val kind = m match { case _: Method => SymbolKind.Method case _: Function => SymbolKind.Function @@ -169,7 +180,7 @@ class FileManager(fileUri: String) { case _ => SymbolKind.Enum } val info: SymbolInformation = new SymbolInformation(m.name, kind, location) - symbolInformation.append(info) + task.symbolInformation.append(info) }) case ProgramDefinitionsReport(defs) => definitions = ArrayBuffer() @@ -196,7 +207,7 @@ class FileManager(fileUri: String) { case StatisticsReport(m, f, p, _, _) => // TODO: pass in task (probably as props to actor). progress = new Progress(p, f, m) - val params = StateChangeParams(VerificationRunning, manuallyTriggered, false, progress = 0, filename = filename) + val params = StateChangeParams(VerificationRunning.id, manuallyTriggered, false, progress = 0, filename = filename) Coordinator.sendStateChangeNotification(params, Some(task)) case EntitySuccessMessage(_, concerning, _, _) => if (progress == null) { @@ -205,7 +216,7 @@ class FileManager(fileUri: String) { val output = BackendOutput(BackendOutputType.FunctionVerified, name = concerning.name) progress.updateProgress(output) val progressPercentage = progress.toPercent - val params = StateChangeParams(VerificationRunning, manuallyTriggered, false, progress = progressPercentage, filename = filename) + val params = StateChangeParams(VerificationRunning.id, manuallyTriggered, false, progress = progressPercentage, filename = filename) Coordinator.sendStateChangeNotification(params, Some(task)) } case EntityFailureMessage(_, _, _, res, _) => @@ -236,8 +247,8 @@ class FileManager(fileUri: String) { diagnostics.append(diag) val params = StateChangeParams( - VerificationRunning, manuallyTriggered, false, filename = filename, - nofErrors = diagnostics.length,uri = fileUri, + VerificationRunning.id, manuallyTriggered, false, filename = filename, + nofErrors = diagnostics.length,uri = file_uri, diagnostics = diagnostics.toArray) Coordinator.sendStateChangeNotification(params, Some(task)) //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) @@ -276,7 +287,7 @@ class FileManager(fileUri: String) { private def completionHandler(code: Int) { try { - Log.debug("completionHandler is called with code ${code}") + Log.debug(s"completionHandler is called with code ${code}") if (is_aborting) { is_verifying = false return @@ -293,8 +304,8 @@ class FileManager(fileUri: String) { if (!isVerifyingStage) { success = Success val params = StateChangeParams( - Ready, manuallyTriggered, false, success = Success, filename = filename, - nofErrors = 0, time = time.toInt, uri = fileUri, error = internalErrorMessage) + Ready.id, manuallyTriggered, false, success = Success.id, filename = filename, + nofErrors = 0, time = time.toInt, uri = file_uri, error = internalErrorMessage) Coordinator.sendStateChangeNotification(params, Some(this)) } else { if (partialData.length > 0) { @@ -306,13 +317,13 @@ class FileManager(fileUri: String) { //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) //inform client about postProcessing - var params = StateChangeParams(PostProcessing, manuallyTriggered, true, filename = filename) + var params = StateChangeParams(PostProcessing.id, manuallyTriggered, true, filename = filename) Coordinator.sendStateChangeNotification(params, Some(this)) //notify client about outcome of verification - params = StateChangeParams(Ready, success = Success, manuallyTriggered = manuallyTriggered, + params = StateChangeParams(Ready.id, success = Success.id, manuallyTriggered = manuallyTriggered, filename = filename, nofErrors = diagnostics.length, time = time.toInt, - verificationCompleted = true, uri = fileUri, error = internalErrorMessage) + verificationCompleted = true, uri = file_uri, error = internalErrorMessage) Coordinator.sendStateChangeNotification(params, Some(this)) if (code != 0 && code != 1 && code != 899) { @@ -327,7 +338,7 @@ class FileManager(fileUri: String) { } catch { case e: Throwable => is_verifying = false - Coordinator.client.notifyVerificationNotStarted(fileUri) + Coordinator.client.notifyVerificationNotStarted(file_uri) Log.debug("Error handling verification completion: ", e) } } diff --git a/src/main/scala/viper/server/IdeLanguageClient.scala b/src/main/scala/viper/server/IdeLanguageClient.scala index 2cc1c8b..844c370 100644 --- a/src/main/scala/viper/server/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/IdeLanguageClient.scala @@ -3,7 +3,7 @@ package viper.server import java.util.concurrent.CompletableFuture import org.eclipse.lsp4j.Position -import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} +import org.eclipse.lsp4j.jsonrpc.services._ import org.eclipse.lsp4j.services.LanguageClient @@ -21,7 +21,10 @@ trait IdeLanguageClient extends LanguageClient { def requestVprFileEndings(): CompletableFuture[Array[String]] @JsonNotification(S2C_Commands.BackendReady) - def notifyBackendReady(uri: String) + def notifyBackendReady(param: BackendReadyParams): Unit + + @JsonNotification(C2S_Commands.StartBackend) + def notifyBackendStarted(name: String, forceRestart: Boolean, isViperServer: Boolean): Unit @JsonNotification(S2C_Commands.BackendChange) def notifyBackendChanged(name: String) @@ -40,30 +43,4 @@ trait IdeLanguageClient extends LanguageClient { @JsonNotification(S2C_Commands.StateChange) def notifyStateChanged(params: StateChangeParams) - - - // @JsonNotification("gobraServer/noVerificationInformation") -// def noVerificationInformation(): Unit -// -// @JsonNotification("gobraServer/overallResult") -// def overallResult(params: String): Unit -// -// @JsonNotification("gobraServer/verificationProgress") -// def verificationProgress(fileUri: String, progress: Int): Unit -// -// @JsonNotification("gobraServer/verificationException") -// def verificationException(fileUri: String): Unit -// -// -// @JsonNotification("gobraServer/finishedGoifying") -// def finishedGoifying(fileUri: String, success: Boolean): Unit -// -// @JsonNotification("gobraServer/finishedGobrafying") -// def finishedGobrafying(oldFilePath: String, newFilePath: String, success: Boolean): Unit -// -// @JsonNotification("gobraServer/finishedViperCodePreview") -// def finishedViperCodePreview(ast: String, highlighted: String): Unit -// -// @JsonNotification("gobraServer/finishedInternalCodePreview") -// def finishedInternalCodePreview(internal: String, highlighted: String): Unit } \ No newline at end of file diff --git a/src/main/scala/viper/server/IdeLog.scala b/src/main/scala/viper/server/IdeLog.scala index 2bfea14..b8ad8c2 100644 --- a/src/main/scala/viper/server/IdeLog.scala +++ b/src/main/scala/viper/server/IdeLog.scala @@ -4,6 +4,7 @@ import viper.server.LogLevel._ object Log { def log(message: String, logLevel: LogLevel) = { + require(message != null && logLevel != null) Coordinator.client.notifyLog(message, logLevel.id) } @@ -11,7 +12,7 @@ object Log { def debug(message: String) = log(message, Debug) - def debug(message: String, error: Throwable) = log(message, Debug) + def debug(message: String, error: Throwable) = log(s"$message $error", Debug) def info(message: String) = log(message, Info) diff --git a/src/main/scala/viper/server/LanguageServerReceiver.scala b/src/main/scala/viper/server/LanguageServerReceiver.scala index 1f84a4c..7ea45c4 100644 --- a/src/main/scala/viper/server/LanguageServerReceiver.scala +++ b/src/main/scala/viper/server/LanguageServerReceiver.scala @@ -5,7 +5,7 @@ import java.util.concurrent.{CompletableFuture => CFuture} import com.google.gson.JsonPrimitive import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware} -import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams} +import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionParams, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} import viper.server.LogLevel._ import scala.collection.JavaConverters._ @@ -19,20 +19,24 @@ class LanguageServerReceiver extends LanguageClientAware { val capabilities = new ServerCapabilities() // OG // always send full text document for each notification: - // capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) // capabilities.setCompletionProvider(new CompletionOptions(true, null)) +// val Coordinator.verifier = new ViperServerService(Array()) +// Coordinator.verifier.setReady(Option.empty) + + capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) capabilities.setDefinitionProvider(true) capabilities.setDocumentSymbolProvider(true) CFuture.completedFuture(new InitializeResult(capabilities)) } - @JsonNotification("textDocument/didChange") + @JsonNotification(value = "textDocument/didOpen") def onDidOpenDocument(params: DidOpenTextDocumentParams): Unit = { + println("On opening document") try { - val uri = params.getTextDocument.getUri + val uri: String = params.getTextDocument.getUri Common.isViperSourceFile(uri).thenAccept(isViperFile => { - if (isViperFile) { + if (true) { //notify client Coordinator.client.notifyFileOpened(uri) if (!Coordinator.files.contains(uri)) { @@ -43,12 +47,28 @@ class LanguageServerReceiver extends LanguageClientAware { } }) } catch { - case e: Throwable => Log.debug("Error handling TextDocument opened") + case e: Throwable => Log.debug("Error handling TextDocument opened", e) } } + @JsonNotification("workspace/didChangeConfiguration") + def onDidChangeConfig(params: DidChangeConfigurationParams): Unit = { + println("On config change") + try { + val b = BackendProperties( + "new Backend", "Silicon", null, null, + 5000, null, 5000, null) + Coordinator.verifier = new ViperServerService(Array()) + Coordinator.verifier.setReady(b) + } catch { + case e: Throwable => Log.debug("Error handling swap backend request: " + e) + } + } + + @JsonNotification("textDocument/didChange") def onDidChangeDocument(params: DidChangeTextDocumentParams): Unit = { + println("On changing document") val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) val manager: FileManager = manager_opt.getOrElse(return) manager.symbolInformation = ArrayBuffer() @@ -57,18 +77,20 @@ class LanguageServerReceiver extends LanguageClientAware { @JsonNotification("textDocument/didClose") def onDidCloseDocument(params: DidCloseTextDocumentParams): Unit = { + println("On closing document") try { val uri = params.getTextDocument.getUri Common.isViperSourceFile(uri).thenAccept(isViperFile => { if (isViperFile) Coordinator.client.notifyFileClosed(uri) }) } catch { - case e: Throwable => Log.debug("Error handling TextDocument opened") + case _: Throwable => Log.debug("Error handling TextDocument opened") } } @JsonNotification(S2C_Commands.FileClosed) def onFileClosed(uri: String): Unit = { + println("On closing file") val manager_opt = Coordinator.files.get(uri) val manager = manager_opt.getOrElse(return) manager.resetDiagnostics() @@ -76,16 +98,16 @@ class LanguageServerReceiver extends LanguageClientAware { } @JsonRequest("textDocument/documentSymbol") - def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[List[SymbolInformation]] = { - var symbolInfo_list: List[SymbolInformation] = List() + def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[Array[SymbolInformation]] = { + var symbolInfo_list: Array[SymbolInformation] = Array() val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) val manager = manager_opt.getOrElse(return CFuture.completedFuture(symbolInfo_list)) - symbolInfo_list = manager.symbolInformation.toList + symbolInfo_list = manager.symbolInformation.toArray CFuture.completedFuture(symbolInfo_list) } @JsonRequest("textDocument/definition") - def onGetDefinition(params: TextDocumentPositionParams): CFuture[List[Location]] = { + def onGetDefinition(params: TextDocumentPositionParams): CFuture[Location] = { Log.log("Handling definitions request for params: " + params.toString, LogLevel.Debug) val document = params.getTextDocument val pos = params.getPosition @@ -96,12 +118,20 @@ class LanguageServerReceiver extends LanguageClientAware { Log.log("Found verification task for URI " + document.getUri, LogLevel.LowLevelDebug) Coordinator.client.requestIdentifier(pos).thenApply(word => { Log.log("Got word: " + word, LowLevelDebug) - manager.definitions.filter(d => (d.scope.getStart == null) //is global - || (Common.comparePosition(d.scope.getStart, pos) <= 0 - && Common.comparePosition(d.scope.getEnd, pos) >= 0)) // in scope - .filter(d => word == d.name) - .map(d => new Location(document.getUri, new Range(d.code_location, d.code_location))) - .toList + def hasGlobalScope(d: Definition) = d.scope.getStart == null + def isPosInScope(d: Definition) = hasGlobalScope(d) || + (Common.comparePosition(d.scope.getStart, pos) <= 0 && + Common.comparePosition(d.scope.getEnd, pos) >= 0) + val defs_in_scope = manager.definitions.filter(isPosInScope) + val matching_def = defs_in_scope.find(d => word == d.name).getOrElse(return null) + val def_range = new Range(matching_def.code_location, matching_def.code_location) + new Location(document.getUri, def_range) +// manager.definitions.filter(d => (d.scope.getStart == null) //is global +// || (Common.comparePosition(d.scope.getStart, pos) <= 0 +// && Common.comparePosition(d.scope.getEnd, pos) >= 0)) // in scope +// .filter(d => word == d.name) +// .map(d => new Location(document.getUri, new Range(d.code_location, d.code_location))) +// .toArray }) case None => // No definition found - maybe it's a keyword. @@ -124,6 +154,21 @@ class LanguageServerReceiver extends LanguageClientAware { CFuture.completedFuture(Coordinator.getAddress) } + @JsonNotification(C2S_Commands.StartBackend) + def onStartBackend(backendName: String): Unit = { + println("Starting ViperServeService") + try { + val b = BackendProperties( + "new Backend", backendName, null, null, + 5000, null, 5000, null) + Coordinator.verifier = new ViperServerService(Array()) + Coordinator.verifier.setReady(b) + } catch { + case e: Throwable => Log.debug("Error handling swap backend request: " + e) + } + } + + @JsonNotification(C2S_Commands.SwapBackend) def onSwapBackend(backendName: String): Unit = { try { @@ -141,14 +186,16 @@ class LanguageServerReceiver extends LanguageClientAware { //it does not make sense to reverify if no changes were made and the verification is already running if (Coordinator.canVerificationBeStarted(data.uri, data.manuallyTriggered)) { // Settings.workspace = data.workspace - Log.log("start or restart verification", LogLevel.Info) //stop all other verifications because the backend crashes if multiple verifications are run in parallel Coordinator.stopAllRunningVerifications().thenAccept(_ => { - //start verification + println("Verifications stopped successfully") Coordinator.executedStages = ArrayBuffer() - val hasVerificationstarted = Coordinator.files - .getOrElse(data.uri, return) - .verify(data.manuallyTriggered) + Log.log("start or restart verification", LogLevel.Info) + + val manager = Coordinator.files.getOrElse(data.uri, return) + val hasVerificationstarted = manager.verify(data.manuallyTriggered) + + Log.log("Verification Started", LogLevel.Info) if (!hasVerificationstarted) { Coordinator.client.notifyVerificationNotStarted(data.uri) } diff --git a/src/main/scala/viper/server/Settings.scala b/src/main/scala/viper/server/Settings.scala index b3c899f..f47d3b1 100644 --- a/src/main/scala/viper/server/Settings.scala +++ b/src/main/scala/viper/server/Settings.scala @@ -1,14 +1,19 @@ -//package viper.server -// -//import java.util.concurrent.CompletableFuture -// -//import scala.concurrent.{ExecutionContext, Future} -//import scala.util.{Failure, Success} +package viper.server + +import java.util.concurrent.{CompletableFuture => CFuture} + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} // //case class ResolvedPath (path: String, exists: Boolean, error: Option[String]) // object Settings { -// implicit val ex = ExecutionContext.global + +// def checkSettings(viperToolsUpdated: Boolean): CFuture[Unit] = ??? +// +// def valid(): Boolean = ??? + + // implicit val ex = ExecutionContext.global // var settings: ViperSettings // var isWin = System.getProperties.get("os.name") // var isLinux = /^linux/.test(process.platform) @@ -180,41 +185,36 @@ object Settings { // } // // //tries to restart backend, -// def initiateBackendRestartIfNeeded(oldSettings?: ViperSettings, selectedBackend?: String, viperToolsUpdated: Boolean = false) { -// Settings.checkSettings(viperToolsUpdated).then(() => { -// if (Settings.valid()) { -// var newBackend = Settings.selectBackend(Settings.settings, selectedBackend); -// -// if (newBackend) { -// //only restart the backend after settings changed if the active backend was affected -// -// Log.log("check if restart needed", LogLevel.LowLevelDebug); -// var backendChanged = !Settings.backendEquals(Coordinator.backend, newBackend) //change in backend -// var mustRestartBackend = !Coordinator.verifier.isReady() //backend is not ready -> restart -// || viperToolsUpdated //Viper Tools Update might have modified the binaries -// || (Coordinator.verifier.isViperServerService != this.useViperServer(newBackend)) //the new backend requires another engine type -// || (Settings.useViperServer(newBackend) && this.viperServerRelatedSettingsChanged(oldSettings)) // the viperServerSettings changed -// if (mustRestartBackend || backendChanged) { -// Log.log(`Change Backend: from ${LanguageServerState.backend ? LanguageServerState.backend.name : "No Backend"} to ${newBackend ? newBackend.name : "No Backend"}`, LogLevel.Info); -// Coordinator.backend = newBackend; -// Coordinator.files.forEach(task => task.resetLastSuccess()); -// Coordinator.sendStartBackendMessage(Coordinator.backend.name, mustRestartBackend, Settings.useViperServer(newBackend)); -// } else { -// Log.log("No need to restart backend. It is still the same", LogLevel.Debug) -// Coordinator.backend = newBackend; -// Coordinator.sendBackendReadyNotification({ -// name: Coordinator.backend.name, -// restarted: false, -// isViperServer: Settings.useViperServer(newBackend) -// }); -// } -// } else { -// Log.debug("No backend, even though the setting check succeeded."); -// } -// } else { -// Coordinator.verifier.stop(); -// } -// }); +// def initiateBackendRestartIfNeeded(oldSettings: Option[ViperSettings], selectedBackend: Option[String], viperToolsUpdated: Boolean = false) { +// checkSettings(viperToolsUpdated).thenApply(() => { +// if (valid()) { +// var newBackend = null +// if (newBackend) { +// //only restart the backend after settings changed if the active backend was affected +// Log.log("check if restart needed", LogLevel.LowLevelDebug); +// val backendChanged = false //change in backend +// val mustRestartBackend = false //backend is not ready -> restart +// if (mustRestartBackend || backendChanged) { +// Log.log(`Change Backend: from ${LanguageServerState.backend ? LanguageServerState.backend.name : "No Backend"} to ${newBackend ? newBackend.name : "No Backend"}`, LogLevel.Info); +// Coordinator.backend = newBackend; +// Coordinator.files.forEach(task => task.resetLastSuccess()); +// Coordinator.sendStartBackendMessage(Coordinator.backend.name, mustRestartBackend, Settings.useViperServer(newBackend)); +// } else { +// Log.log("No need to restart backend. It is still the same", LogLevel.Debug) +// Coordinator.backend = newBackend; +// Coordinator.sendBackendReadyNotification({ +// name: Coordinator.backend.name, +// restarted: false, +// isViperServer: Settings.useViperServer(newBackend) +// }); +// } +// } else { +// Log.debug("No backend, even though the setting check succeeded."); +// } +// } else { +// Coordinator.verifier.stop(); +// } +// }); // } // // private def addError(msg: String) { diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index 1e7a944..78760cc 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -37,7 +37,7 @@ object ViperServerRunner { val server: LanguageServerReceiver = new LanguageServerReceiver() - val launcher = Launcher.createLauncher(server, classOf[LanguageClient], socket.getInputStream, socket.getOutputStream) + val launcher = Launcher.createLauncher(server, classOf[IdeLanguageClient], socket.getInputStream, socket.getOutputStream) server.connect(launcher.getRemoteProxy) // start listening on input stream in a new thread: diff --git a/src/main/scala/viper/server/ViperServerService.scala b/src/main/scala/viper/server/ViperServerService.scala index 58d0160..5086950 100644 --- a/src/main/scala/viper/server/ViperServerService.scala +++ b/src/main/scala/viper/server/ViperServerService.scala @@ -18,36 +18,39 @@ import scala.concurrent.duration._ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { - private var _ready: Boolean = false - protected var timeout: Int = _ - def isReady: Boolean = _ready + def isReady: Boolean = isRunning def setReady(backend: BackendProperties): Unit = { - _ready = true Coordinator.backend = backend - Coordinator.startingOrRestarting = false + start() + Coordinator.startingOrRestarting = true + val param = BackendReadyParams("Silicon", false, true) + Coordinator.client.notifyBackendReady(param) Log.info("The backend is ready for verification") - Coordinator.client.notifyBackendReady(S2C_Commands.BackendReady) } def swapBackend(newBackend: BackendProperties): Unit = { - setReady(newBackend) + Coordinator.backend = newBackend + Coordinator.startingOrRestarting = false + val param = BackendReadyParams("Silicon", false, true) + Coordinator.client.notifyBackendReady(param) + Log.info("The backend has been swapped and is now ready for verification") } def setStopping(): Unit = { Log.debug("Set Stopping... ") - _ready = false + isRunning = false Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopping, false, false), None) + Coordinator.sendStateChangeNotification(StateChangeParams(Stopping.id, false, false), None) } def setStopped(): Unit = { Log.debug("Set Stopped. ") - _ready = false + isRunning = false Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopped, false, false), None) + Coordinator.sendStateChangeNotification(StateChangeParams(Stopped.id, false, false), None) } private def getArgListFromArgString(arg_str: String): List[String] = { @@ -61,7 +64,6 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { def startVerification(command: String): Int = { Log.debug("Requesting ViperServer to start new job...") - // Todo start verification in VCS val arg_list = getArgListFromArgString(command) val file: String = arg_list.last @@ -75,7 +77,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } catch { case _: java.nio.file.NoSuchFileException => Log.debug("The file for which verification has been requested was not found.") - return-1 + return -1 } val ast = ast_option.getOrElse({ Log.debug("The file for which verification has been requested contained syntax errors.") From bd49a930ed4e15b7d44713db6c99c9dcbca453ce Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 12 Oct 2020 17:53:16 +0200 Subject: [PATCH 05/79] verification cycle error-free --- .../scala/viper/server/DataProtocol.scala | 12 ++-- src/main/scala/viper/server/FileManager.scala | 60 ++++++++++--------- .../viper/server/IdeLanguageClient.scala | 15 +++-- .../viper/server/LanguageServerReceiver.scala | 37 +++++++----- .../viper/server/ViperServerService.scala | 6 +- 5 files changed, 73 insertions(+), 57 deletions(-) diff --git a/src/main/scala/viper/server/DataProtocol.scala b/src/main/scala/viper/server/DataProtocol.scala index cc9ce07..8823198 100644 --- a/src/main/scala/viper/server/DataProtocol.scala +++ b/src/main/scala/viper/server/DataProtocol.scala @@ -52,18 +52,18 @@ case class ProgressReport ( case class Hint(msg: String, showButton1: Boolean, showButton2: Boolean) -//Might need to change assignments +//Might case class StateChangeParams( - newState: Double, - manuallyTriggered: Boolean, //should be Boolean - verificationCompleted: Boolean, //should be Boolean + newState: Int, progress: Double = -1, - success: Double = NA.id, + success: Int = NA.id, + verificationCompleted: Double = -1, + manuallyTriggered: Double = -1, filename: String = null, backendName: String = null, time: Double = -1, nofErrors: Double = -1, -// verificationNeeded: Double = Double.NaN, //should be Boolean + verificationNeeded: Double = -1, uri: String = null, stage: String = null, error: String = null, diff --git a/src/main/scala/viper/server/FileManager.scala b/src/main/scala/viper/server/FileManager.scala index 9e3d0fa..afa95a0 100644 --- a/src/main/scala/viper/server/FileManager.scala +++ b/src/main/scala/viper/server/FileManager.scala @@ -40,7 +40,7 @@ class FileManager(file_uri: String) { var progress: Progress = _ // var shownExecutionTrace: Array[ExecutionTrace] = _ var symbolInformation: ArrayBuffer[SymbolInformation] = ArrayBuffer() - var definitions: ArrayBuffer[Definition] = _ + var definitions: ArrayBuffer[Definition] = ArrayBuffer() //working variables private var lines: Array[String] = Array() @@ -124,7 +124,7 @@ class FileManager(file_uri: String) { } } - def verify(manuallyTriggered: Boolean): Boolean = { + def startVerification(manuallyTriggered: Boolean): Boolean = { prepareVerification() this.manuallyTriggered = manuallyTriggered @@ -142,15 +142,19 @@ class FileManager(file_uri: String) { Log.info("verify " + filename) Log.info(Coordinator.backend.name + " verification started") - val params = StateChangeParams(VerificationRunning.id, manuallyTriggered, false, filename = filename) + val params = StateChangeParams(VerificationRunning.id, filename = filename) + println("state change params created") Coordinator.sendStateChangeNotification(params, Some(this)) + println("state change params sent") // val command = startStageProcess(stage, path).getOrElse(return false) val command = startStageProcess(path.toString).getOrElse(return false) println(s"Successfully generated command: $command") - jid = Coordinator.verifier.startVerification(command) + jid = Coordinator.verifier.verify(command) println(s"Successfully started verifying with id: $jid") + Log.debug(s"ViperServer started new job with ID ${jid}") Coordinator.verifier.startStreaming(jid, RelayActor.props(this)) + Log.debug(s"Requesting ViperServer to stream results of verification job #${jid}...") true } @@ -202,12 +206,11 @@ class FileManager(file_uri: String) { val location: Position = new Position(sourcePos.start.line, sourcePos.start.column) val definition: Definition = Definition(d.typ, d.name, location, range) definitions.append(definition) - //Log.log("Definition: " + JSON.stringify(definition), LogLevel.LowLevelDebug) }) case StatisticsReport(m, f, p, _, _) => // TODO: pass in task (probably as props to actor). progress = new Progress(p, f, m) - val params = StateChangeParams(VerificationRunning.id, manuallyTriggered, false, progress = 0, filename = filename) + val params = StateChangeParams(VerificationRunning.id, progress = 0, filename = filename) Coordinator.sendStateChangeNotification(params, Some(task)) case EntitySuccessMessage(_, concerning, _, _) => if (progress == null) { @@ -216,7 +219,7 @@ class FileManager(file_uri: String) { val output = BackendOutput(BackendOutputType.FunctionVerified, name = concerning.name) progress.updateProgress(output) val progressPercentage = progress.toPercent - val params = StateChangeParams(VerificationRunning.id, manuallyTriggered, false, progress = progressPercentage, filename = filename) + val params = StateChangeParams(VerificationRunning.id, progress = progressPercentage, filename = filename) Coordinator.sendStateChangeNotification(params, Some(task)) } case EntityFailureMessage(_, _, _, res, _) => @@ -247,9 +250,8 @@ class FileManager(file_uri: String) { diagnostics.append(diag) val params = StateChangeParams( - VerificationRunning.id, manuallyTriggered, false, filename = filename, - nofErrors = diagnostics.length,uri = file_uri, - diagnostics = diagnostics.toArray) + VerificationRunning.id, filename = filename, nofErrors = diagnostics.length, + uri = file_uri, diagnostics = diagnostics.toArray) Coordinator.sendStateChangeNotification(params, Some(task)) //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) }) @@ -261,6 +263,7 @@ class FileManager(file_uri: String) { state = VerificationReporting time = verificationTime completionHandler(0) + case m: Message => Coordinator.client.notifyUnhandledViperServerMessage(m.toString, 2) case e: Throwable => //receiving an error means the promise can be finalized with failure. } @@ -292,23 +295,15 @@ class FileManager(file_uri: String) { is_verifying = false return } + var params: StateChangeParams = null var success = NA - val isVerifyingStage = Coordinator.stage.getOrElse(return).isVerification + // val isVerifyingStage = Coordinator.stage.getOrElse(return).isVerification + val isVerifyingStage = true //do we need to start a followUp Stage? - if (!is_aborting && isVerifyingStage) { - success = determineSuccess(code) - } - - if (!isVerifyingStage) { - success = Success - val params = StateChangeParams( - Ready.id, manuallyTriggered, false, success = Success.id, filename = filename, - nofErrors = 0, time = time.toInt, uri = file_uri, error = internalErrorMessage) - Coordinator.sendStateChangeNotification(params, Some(this)) - } else { - if (partialData.length > 0) { + if (isVerifyingStage) { + if (partialData != null && partialData.length > 0) { Log.debug("Some unparsed output was detected:\n" + partialData) partialData = "" } @@ -317,18 +312,27 @@ class FileManager(file_uri: String) { //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) //inform client about postProcessing - var params = StateChangeParams(PostProcessing.id, manuallyTriggered, true, filename = filename) - Coordinator.sendStateChangeNotification(params, Some(this)) + success = determineSuccess(code) + params = StateChangeParams(PostProcessing.id, filename = filename) + Coordinator.sendStateChangeNotification(params, Some(this)); //notify client about outcome of verification - params = StateChangeParams(Ready.id, success = Success.id, manuallyTriggered = manuallyTriggered, - filename = filename, nofErrors = diagnostics.length, time = time.toInt, - verificationCompleted = true, uri = file_uri, error = internalErrorMessage) + val mt = if(this.manuallyTriggered) 1 else 0 + params = StateChangeParams(Ready.id, success = success.id, manuallyTriggered = mt, + filename = filename, nofErrors = diagnostics.length, time = time.toDouble, + verificationCompleted = 1, uri = file_uri, error = internalErrorMessage) Coordinator.sendStateChangeNotification(params, Some(this)) if (code != 0 && code != 1 && code != 899) { Log.debug("Verification Backend Terminated Abnormally: with code " + code) } + } else { + success = Success + val mt = if(this.manuallyTriggered) 1 else 0 + params = StateChangeParams(Ready.id, success = success.id, manuallyTriggered = mt, + filename = filename, nofErrors = 0, time = time.toDouble, + verificationCompleted = 0, uri = file_uri, error = internalErrorMessage) + Coordinator.sendStateChangeNotification(params, Some(this)) } //reset for next verification diff --git a/src/main/scala/viper/server/IdeLanguageClient.scala b/src/main/scala/viper/server/IdeLanguageClient.scala index 844c370..0de5bdc 100644 --- a/src/main/scala/viper/server/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/IdeLanguageClient.scala @@ -27,20 +27,23 @@ trait IdeLanguageClient extends LanguageClient { def notifyBackendStarted(name: String, forceRestart: Boolean, isViperServer: Boolean): Unit @JsonNotification(S2C_Commands.BackendChange) - def notifyBackendChanged(name: String) + def notifyBackendChanged(name: String): Unit @JsonNotification(S2C_Commands.Progress) - def notifyProgress(progress: ProgressReport, logLevel: Int) + def notifyProgress(progress: ProgressReport, logLevel: Int): Unit @JsonNotification(S2C_Commands.Log) - def notifyLog(msg: String, logLevel: Int) + def notifyLog(msg: String, logLevel: Int): Unit @JsonNotification(S2C_Commands.Hint) - def notifyHint(msg: String, hint: Hint) + def notifyHint(msg: String, hint: Hint): Unit + + @JsonNotification(S2C_Commands.UnhandledViperServerMessageType) + def notifyUnhandledViperServerMessage(msg: String, logLevel: Int): Unit @JsonNotification(S2C_Commands.VerificationNotStarted) - def notifyVerificationNotStarted(uri: String) + def notifyVerificationNotStarted(uri: String): Unit @JsonNotification(S2C_Commands.StateChange) - def notifyStateChanged(params: StateChangeParams) + def notifyStateChanged(params: StateChangeParams): Unit } \ No newline at end of file diff --git a/src/main/scala/viper/server/LanguageServerReceiver.scala b/src/main/scala/viper/server/LanguageServerReceiver.scala index 7ea45c4..ffcadd5 100644 --- a/src/main/scala/viper/server/LanguageServerReceiver.scala +++ b/src/main/scala/viper/server/LanguageServerReceiver.scala @@ -7,6 +7,7 @@ import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware} import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionParams, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} import viper.server.LogLevel._ +import viper.server.VerificationState._ import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer @@ -118,7 +119,7 @@ class LanguageServerReceiver extends LanguageClientAware { Log.log("Found verification task for URI " + document.getUri, LogLevel.LowLevelDebug) Coordinator.client.requestIdentifier(pos).thenApply(word => { Log.log("Got word: " + word, LowLevelDebug) - def hasGlobalScope(d: Definition) = d.scope.getStart == null + def hasGlobalScope(d: Definition) = d.scope == null def isPosInScope(d: Definition) = hasGlobalScope(d) || (Common.comparePosition(d.scope.getStart, pos) <= 0 && Common.comparePosition(d.scope.getEnd, pos) >= 0) @@ -126,12 +127,6 @@ class LanguageServerReceiver extends LanguageClientAware { val matching_def = defs_in_scope.find(d => word == d.name).getOrElse(return null) val def_range = new Range(matching_def.code_location, matching_def.code_location) new Location(document.getUri, def_range) -// manager.definitions.filter(d => (d.scope.getStart == null) //is global -// || (Common.comparePosition(d.scope.getStart, pos) <= 0 -// && Common.comparePosition(d.scope.getEnd, pos) >= 0)) // in scope -// .filter(d => word == d.name) -// .map(d => new Location(document.getUri, new Range(d.code_location, d.code_location))) -// .toArray }) case None => // No definition found - maybe it's a keyword. @@ -168,7 +163,6 @@ class LanguageServerReceiver extends LanguageClientAware { } } - @JsonNotification(C2S_Commands.SwapBackend) def onSwapBackend(backendName: String): Unit = { try { @@ -193,7 +187,7 @@ class LanguageServerReceiver extends LanguageClientAware { Log.log("start or restart verification", LogLevel.Info) val manager = Coordinator.files.getOrElse(data.uri, return) - val hasVerificationstarted = manager.verify(data.manuallyTriggered) + val hasVerificationstarted = manager.startVerification(data.manuallyTriggered) Log.log("Verification Started", LogLevel.Info) if (!hasVerificationstarted) { @@ -215,6 +209,24 @@ class LanguageServerReceiver extends LanguageClientAware { println("flushing cache...") } + @JsonNotification(C2S_Commands.StopVerification) + def onStopVerification(uri: String): CFuture[Boolean] = { + println("on stopping verification") + try { + val manager = Coordinator.files.getOrElse(uri, return CFuture.completedFuture(false)) + manager.abortVerification().thenApply((success) => { + val params = StateChangeParams(Ready.id, verificationCompleted = 0, verificationNeeded = 0, uri = uri) + Coordinator.sendStateChangeNotification(params, Some(manager)) + true + }) + } catch { + case e => + Log.debug("Error handling stop verification request (critical): " + e); + CFuture.completedFuture(false) + } + } + + @JsonRequest(value = "shutdown") def shutdown(): CFuture[AnyRef] = { println("shutdown") @@ -229,13 +241,10 @@ class LanguageServerReceiver extends LanguageClientAware { @JsonRequest("textDocument/completion") def completion(params: CompletionParams): CFuture[CompletionList] = { - val tsItem = new CompletionItem("TypeScript") + val tsItem = new CompletionItem("Do the completion for Viper!!") tsItem.setKind(CompletionItemKind.Text) tsItem.setData(1) - val jsItem = new CompletionItem("JavaScript") - jsItem.setKind(CompletionItemKind.Text) - jsItem.setData(2) - val completions = new CompletionList(List(tsItem, jsItem).asJava) + val completions = new CompletionList(List(tsItem).asJava) CFuture.completedFuture(completions) } diff --git a/src/main/scala/viper/server/ViperServerService.scala b/src/main/scala/viper/server/ViperServerService.scala index 5086950..b31a5e1 100644 --- a/src/main/scala/viper/server/ViperServerService.scala +++ b/src/main/scala/viper/server/ViperServerService.scala @@ -43,14 +43,14 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { Log.debug("Set Stopping... ") isRunning = false Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopping.id, false, false), None) + Coordinator.sendStateChangeNotification(StateChangeParams(Stopping.id), None) } def setStopped(): Unit = { Log.debug("Set Stopped. ") isRunning = false Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopped.id, false, false), None) + Coordinator.sendStateChangeNotification(StateChangeParams(Stopped.id), None) } private def getArgListFromArgString(arg_str: String): List[String] = { @@ -62,7 +62,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } } - def startVerification(command: String): Int = { + def verify(command: String): Int = { Log.debug("Requesting ViperServer to start new job...") val arg_list = getArgListFromArgString(command) From c4f4e9e9bc5a6a255cac65f498da346f48f775a7 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 14:45:32 +0200 Subject: [PATCH 06/79] added flushing and proper server termination --- src/main/scala/viper/server/Coordinator.scala | 16 +- ...ageServerReceiver.scala => Receiver.scala} | 149 +++++++++--------- src/main/scala/viper/server/ViperServer.scala | 2 +- .../viper/server/ViperServerService.scala | 99 ++++-------- 4 files changed, 116 insertions(+), 150 deletions(-) rename src/main/scala/viper/server/{LanguageServerReceiver.scala => Receiver.scala} (82%) diff --git a/src/main/scala/viper/server/Coordinator.scala b/src/main/scala/viper/server/Coordinator.scala index d93df6d..fd6cf4e 100644 --- a/src/main/scala/viper/server/Coordinator.scala +++ b/src/main/scala/viper/server/Coordinator.scala @@ -14,7 +14,7 @@ object Coordinator { // var tempDirectory: String = pathHelper.join(os.tmpdir(), ".vscode") = _ // var backendOutputDirectory: String = os.tmpdir() = _ - var executedStages: ArrayBuffer[Stage] = _ +// var executedStages: ArrayBuffer[Stage] = _ var documents: TextDocumentItem = new TextDocumentItem() var files = mutable.Map.empty[String, FileManager] @@ -24,13 +24,13 @@ object Coordinator { def getAddress: String = url + ":" + port - def stage: Option[Stage] = { - if (executedStages != null && this.executedStages.nonEmpty) { - Some(executedStages(executedStages.length - 1)) - } else { - None - } - } +// def stage: Option[Stage] = { +// if (executedStages != null && this.executedStages.nonEmpty) { +// Some(executedStages(executedStages.length - 1)) +// } else { +// None +// } +// } def canVerificationBeStarted(uri: String, manuallyTriggered: Boolean): Boolean = { //check if there is already a verification task for that file diff --git a/src/main/scala/viper/server/LanguageServerReceiver.scala b/src/main/scala/viper/server/Receiver.scala similarity index 82% rename from src/main/scala/viper/server/LanguageServerReceiver.scala rename to src/main/scala/viper/server/Receiver.scala index ffcadd5..99a3e47 100644 --- a/src/main/scala/viper/server/LanguageServerReceiver.scala +++ b/src/main/scala/viper/server/Receiver.scala @@ -2,7 +2,6 @@ package viper.server import java.util.concurrent.{CompletableFuture => CFuture} -import com.google.gson.JsonPrimitive import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware} import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionParams, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} @@ -12,9 +11,10 @@ import viper.server.VerificationState._ import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer -class LanguageServerReceiver extends LanguageClientAware { +abstract class StandardReceiver extends LanguageClientAware { + var received_shutdown = false - @JsonRequest(value = "initialize") + @JsonRequest("initialize") def initialize(params: InitializeParams): CFuture[InitializeResult] = { println("initialize") val capabilities = new ServerCapabilities() @@ -22,8 +22,8 @@ class LanguageServerReceiver extends LanguageClientAware { // always send full text document for each notification: // capabilities.setCompletionProvider(new CompletionOptions(true, null)) -// val Coordinator.verifier = new ViperServerService(Array()) -// Coordinator.verifier.setReady(Option.empty) + // val Coordinator.verifier = new ViperServerService(Array()) + // Coordinator.verifier.setReady(Option.empty) capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) capabilities.setDefinitionProvider(true) @@ -31,7 +31,7 @@ class LanguageServerReceiver extends LanguageClientAware { CFuture.completedFuture(new InitializeResult(capabilities)) } - @JsonNotification(value = "textDocument/didOpen") + @JsonNotification("textDocument/didOpen") def onDidOpenDocument(params: DidOpenTextDocumentParams): Unit = { println("On opening document") try { @@ -66,7 +66,6 @@ class LanguageServerReceiver extends LanguageClientAware { } } - @JsonNotification("textDocument/didChange") def onDidChangeDocument(params: DidChangeTextDocumentParams): Unit = { println("On changing document") @@ -76,28 +75,6 @@ class LanguageServerReceiver extends LanguageClientAware { manager.definitions = ArrayBuffer() } - @JsonNotification("textDocument/didClose") - def onDidCloseDocument(params: DidCloseTextDocumentParams): Unit = { - println("On closing document") - try { - val uri = params.getTextDocument.getUri - Common.isViperSourceFile(uri).thenAccept(isViperFile => { - if (isViperFile) Coordinator.client.notifyFileClosed(uri) - }) - } catch { - case _: Throwable => Log.debug("Error handling TextDocument opened") - } - } - - @JsonNotification(S2C_Commands.FileClosed) - def onFileClosed(uri: String): Unit = { - println("On closing file") - val manager_opt = Coordinator.files.get(uri) - val manager = manager_opt.getOrElse(return) - manager.resetDiagnostics() - Coordinator.files -= uri - } - @JsonRequest("textDocument/documentSymbol") def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[Array[SymbolInformation]] = { var symbolInfo_list: Array[SymbolInformation] = Array() @@ -121,8 +98,8 @@ class LanguageServerReceiver extends LanguageClientAware { Log.log("Got word: " + word, LowLevelDebug) def hasGlobalScope(d: Definition) = d.scope == null def isPosInScope(d: Definition) = hasGlobalScope(d) || - (Common.comparePosition(d.scope.getStart, pos) <= 0 && - Common.comparePosition(d.scope.getEnd, pos) >= 0) + (Common.comparePosition(d.scope.getStart, pos) <= 0 && + Common.comparePosition(d.scope.getEnd, pos) >= 0) val defs_in_scope = manager.definitions.filter(isPosInScope) val matching_def = defs_in_scope.find(d => word == d.name).getOrElse(return null) val def_range = new Range(matching_def.code_location, matching_def.code_location) @@ -136,6 +113,70 @@ class LanguageServerReceiver extends LanguageClientAware { } } + @JsonNotification("textDocument/didClose") + def onDidCloseDocument(params: DidCloseTextDocumentParams): Unit = { + println("On closing document") + try { + val uri = params.getTextDocument.getUri + Common.isViperSourceFile(uri).thenAccept(isViperFile => { + if (isViperFile) Coordinator.client.notifyFileClosed(uri) + }) + } catch { + case _: Throwable => Log.debug("Error handling TextDocument opened") + } + } + + @JsonRequest(value = "shutdown") + def onShutdown(): CFuture[AnyRef] = { + println("shutdown") + received_shutdown = true + CFuture.completedFuture(null) + } + + @JsonNotification(value = "exit") + def onExit(): Unit = { + println("exit") + Coordinator.verifier.stop() + if(received_shutdown) sys.exit(0) else sys.exit(1) + } + +// @JsonRequest("textDocument/completion") +// def completion(params: CompletionParams): CFuture[CompletionList] = { +// val tsItem = new CompletionItem("Do the completion for Viper!!") +// tsItem.setKind(CompletionItemKind.Text) +// tsItem.setData(1) +// val completions = new CompletionList(List(tsItem).asJava) +// CFuture.completedFuture(completions) +// } +// +// @JsonRequest("completionItem/resolve") +// def completionItemResolve(item: CompletionItem): CFuture[CompletionItem] = { +// val data: Object = item.getData +// data match { +// case n: JsonPrimitive if n.getAsInt == 1 => +// item.setDetail("TypeScript details") +// item.setDocumentation("TypeScript documentation") +// case n: JsonPrimitive if n.getAsInt == 2 => +// item.setDetail("JavaScript details") +// item.setDocumentation("JavaScript documentation") +// case _ => +// item.setDetail(s"${data.toString} is instance of ${data.getClass}") +// } +// CFuture.completedFuture(item) +// } +} + +class CustomReceiver extends StandardReceiver { + + @JsonNotification(S2C_Commands.FileClosed) + def onFileClosed(uri: String): Unit = { + println("On closing file") + val manager_opt = Coordinator.files.get(uri) + val manager = manager_opt.getOrElse(return) + manager.resetDiagnostics() + Coordinator.files -= uri + } + @JsonRequest(C2S_Commands.RemoveDiagnostics) def onRemoveDiagnostics(uri: String): CFuture[Boolean] = { val manager_opt = Coordinator.files.get(uri) @@ -183,7 +224,7 @@ class LanguageServerReceiver extends LanguageClientAware { //stop all other verifications because the backend crashes if multiple verifications are run in parallel Coordinator.stopAllRunningVerifications().thenAccept(_ => { println("Verifications stopped successfully") - Coordinator.executedStages = ArrayBuffer() +// Coordinator.executedStages = ArrayBuffer() Log.log("start or restart verification", LogLevel.Info) val manager = Coordinator.files.getOrElse(data.uri, return) @@ -205,8 +246,10 @@ class LanguageServerReceiver extends LanguageClientAware { } @JsonNotification(C2S_Commands.FlushCache) - def flushCache(file: String): Unit = { + def onFlushCache(file: String): Unit = { println("flushing cache...") + val file_arg: Option[String] = if(file == null) Option.empty else Some(file) + Coordinator.verifier.flushCache(file_arg) } @JsonNotification(C2S_Commands.StopVerification) @@ -220,50 +263,12 @@ class LanguageServerReceiver extends LanguageClientAware { true }) } catch { - case e => + case e: Throwable => Log.debug("Error handling stop verification request (critical): " + e); CFuture.completedFuture(false) } } - - @JsonRequest(value = "shutdown") - def shutdown(): CFuture[AnyRef] = { - println("shutdown") - CFuture.completedFuture(null) - } - - @JsonNotification(value = "exit") - def exit(): Unit = { - println("exit") - sys.exit() - } - - @JsonRequest("textDocument/completion") - def completion(params: CompletionParams): CFuture[CompletionList] = { - val tsItem = new CompletionItem("Do the completion for Viper!!") - tsItem.setKind(CompletionItemKind.Text) - tsItem.setData(1) - val completions = new CompletionList(List(tsItem).asJava) - CFuture.completedFuture(completions) - } - - @JsonRequest("completionItem/resolve") - def completionItemResolve(item: CompletionItem): CFuture[CompletionItem] = { - val data: Object = item.getData - data match { - case n: JsonPrimitive if n.getAsInt == 1 => - item.setDetail("TypeScript details") - item.setDocumentation("TypeScript documentation") - case n: JsonPrimitive if n.getAsInt == 2 => - item.setDetail("JavaScript details") - item.setDocumentation("JavaScript documentation") - case _ => - item.setDetail(s"${data.toString} is instance of ${data.getClass}") - } - CFuture.completedFuture(item) - } - override def connect(client: LanguageClient): Unit = { println("Connecting plugin client.") val c = client.asInstanceOf[IdeLanguageClient] diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index 78760cc..e2efe0b 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -36,7 +36,7 @@ object ViperServerRunner { Coordinator.url = localAddress - val server: LanguageServerReceiver = new LanguageServerReceiver() + val server: CustomReceiver = new CustomReceiver() val launcher = Launcher.createLauncher(server, classOf[IdeLanguageClient], socket.getInputStream, socket.getOutputStream) server.connect(launcher.getRemoteProxy) diff --git a/src/main/scala/viper/server/ViperServerService.scala b/src/main/scala/viper/server/ViperServerService.scala index b31a5e1..b9c25de 100644 --- a/src/main/scala/viper/server/ViperServerService.scala +++ b/src/main/scala/viper/server/ViperServerService.scala @@ -7,7 +7,7 @@ import akka.pattern.ask import akka.util.Timeout import viper.server.VerificationState._ import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} -import viper.server.core.{VerificationJobHandler, ViperCoreServer} +import viper.server.core.{VerificationJobHandler, ViperCache, ViperCoreServer} import viper.server.protocol.ViperServerProtocol.Stop import viper.server.utility.AstGenerator import viper.silver.ast.Program @@ -39,19 +39,19 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { Log.info("The backend has been swapped and is now ready for verification") } - def setStopping(): Unit = { - Log.debug("Set Stopping... ") - isRunning = false - Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopping.id), None) - } - - def setStopped(): Unit = { - Log.debug("Set Stopped. ") - isRunning = false - Coordinator.startingOrRestarting = false - Coordinator.sendStateChangeNotification(StateChangeParams(Stopped.id), None) - } +// def setStopping(): Unit = { +// Log.debug("Set Stopping... ") +// isRunning = false +// Coordinator.startingOrRestarting = false +// Coordinator.sendStateChangeNotification(StateChangeParams(Stopping.id), None) +// } +// +// def setStopped(): Unit = { +// Log.debug("Set Stopped. ") +// isRunning = false +// Coordinator.startingOrRestarting = false +// Coordinator.sendStateChangeNotification(StateChangeParams(Stopped.id), None) +// } private def getArgListFromArgString(arg_str: String): List[String] = { val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r @@ -127,61 +127,22 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } } -// def flushCache(filePath?: String): CFuture[String] = { -// return new Promise((resolve, reject) => { -// val url = this._url + ':' + this._port + '/cache/flush' -// if (filePath) { -// Log.log(`Requesting ViperServer to flush the cache for (` + filePath + `)...`, LogLevel.Info) -// -// val options = { -// url: url, -// headers: {'content-type': 'application/json'}, -// body: JSON.stringify({ backend: Coordinator.backend.name, file: filePath }) -// } -// -// request.post(options).on('error', (error) => { -// Log.log(`error while requesting ViperServer to flush the cache for (` + filePath + `).` + -// ` Request URL: ${url}\n` + -// ` Error message: ${error}`, LogLevel.Default) -// reject(error) -// -// }).on('data', (data) => { -// val response = JSON.parse(data.toString()) -// if ( !response.msg ) { -// Log.log(`ViperServer did not complain about the way we requested it to flush the cache for (` + filePath + `).` + -// ` However, it also did not provide the expected bye-bye message.` + -// ` It said: ${data.toString}`, LogLevel.Debug) -// resolve(response) -// } else { -// Log.log(`ViperServer has confirmed that the cache for (` + filePath + `) has been flushed.`, LogLevel.Debug) -// resolve(response.msg) -// } -// }) -// -// } else { -// Log.log(`Requesting ViperServer to flush the entire cache...`, LogLevel.Info) -// -// request.get(url).on('error', (error) => { -// Log.log(`error while requesting ViperServer to flush the entire cache.` + -// ` Request URL: ${url}\n` + -// ` Error message: ${error}`, LogLevel.Default) -// reject(error) -// -// }).on('data', (data) => { -// val response = JSON.parse(data.toString()) -// if ( !response.msg ) { -// Log.log(`ViperServer did not complain about the way we requested it to flush the entire cache.` + -// ` However, it also did not provide the expected bye-bye message.` + -// ` It said: ${data.toString}`, LogLevel.Debug) -// resolve(response) -// } else { -// Log.log(`ViperServer has confirmed that the entire cache has been flushed.`, LogLevel.Debug) -// resolve(response.msg) -// } -// }) -// } -// }) -// } + def flushCache(filePath: Option[String]): Unit = { + filePath match { + case Some(file) => + Log.info(s"Requesting ViperServer to flush the cache for $file...") + val flushed_file_opt = ViperCache.forgetFile("silicon", file) + + if (flushed_file_opt.isDefined) { + Log.debug(s"ViperServer has confirmed that the cache for $file has been flushed.") + } else { + Log.debug(s"Error while requesting ViperServer to flush the cache for $file: File not found.") + } + case None => + Log.info("Flush entire cache...") + super.flushCache() + } + } def isSupportedType(t: String): Boolean = { if (t == null) { From 6b81b69db149fd6e1f38a0c4bc6e8606b6a2cc0a Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 15:45:42 +0200 Subject: [PATCH 07/79] moved language server files to their intended directory --- src/main/scala/viper/server/ViperServer.scala | 1 + .../{ => frontends/lsp}/CommandProtocol.scala | 10 +++++++- .../server/{ => frontends/lsp}/Common.scala | 10 +++++++- .../{ => frontends/lsp}/Coordinator.scala | 10 +++++++- .../{ => frontends/lsp}/DataProtocol.scala | 15 ++++++++---- .../{ => frontends/lsp}/FileManager.scala | 19 +++++++++++---- .../lsp}/IdeLanguageClient.scala | 10 +++++++- .../server/{ => frontends/lsp}/IdeLog.scala | 12 ++++++++-- .../server/{ => frontends/lsp}/Progress.scala | 4 ++-- .../server/{ => frontends/lsp}/Receiver.scala | 23 +++++++++++++++---- .../server/{ => frontends/lsp}/Settings.scala | 10 +++++++- .../lsp}/ViperServerService.scala | 11 +++++++-- 12 files changed, 110 insertions(+), 25 deletions(-) rename src/main/scala/viper/server/{ => frontends/lsp}/CommandProtocol.scala (87%) rename src/main/scala/viper/server/{ => frontends/lsp}/Common.scala (97%) rename src/main/scala/viper/server/{ => frontends/lsp}/Coordinator.scala (89%) rename src/main/scala/viper/server/{ => frontends/lsp}/DataProtocol.scala (95%) rename src/main/scala/viper/server/{ => frontends/lsp}/FileManager.scala (96%) rename src/main/scala/viper/server/{ => frontends/lsp}/IdeLanguageClient.scala (84%) rename src/main/scala/viper/server/{ => frontends/lsp}/IdeLog.scala (83%) rename src/main/scala/viper/server/{ => frontends/lsp}/Progress.scala (92%) rename src/main/scala/viper/server/{ => frontends/lsp}/Receiver.scala (92%) rename src/main/scala/viper/server/{ => frontends/lsp}/Settings.scala (99%) rename src/main/scala/viper/server/{ => frontends/lsp}/ViperServerService.scala (94%) diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index e2efe0b..649d64e 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -5,6 +5,7 @@ import java.net.Socket import org.eclipse.lsp4j.jsonrpc.Launcher import org.eclipse.lsp4j.services.LanguageClient +import viper.server.frontends.lsp.{Coordinator, CustomReceiver, IdeLanguageClient} object ViperServerRunner { diff --git a/src/main/scala/viper/server/CommandProtocol.scala b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala similarity index 87% rename from src/main/scala/viper/server/CommandProtocol.scala rename to src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala index 49f217e..b8e2fdb 100644 --- a/src/main/scala/viper/server/CommandProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp object C2S_Commands { final val RequestBackendNames = "RequestBackendNames" //void diff --git a/src/main/scala/viper/server/Common.scala b/src/main/scala/viper/server/frontends/lsp/Common.scala similarity index 97% rename from src/main/scala/viper/server/Common.scala rename to src/main/scala/viper/server/frontends/lsp/Common.scala index e96e093..9b73f27 100644 --- a/src/main/scala/viper/server/Common.scala +++ b/src/main/scala/viper/server/frontends/lsp/Common.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.net.URI import java.nio.file.{Path, Paths} diff --git a/src/main/scala/viper/server/Coordinator.scala b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala similarity index 89% rename from src/main/scala/viper/server/Coordinator.scala rename to src/main/scala/viper/server/frontends/lsp/Coordinator.scala index fd6cf4e..4fce3d8 100644 --- a/src/main/scala/viper/server/Coordinator.scala +++ b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.util.concurrent.CompletableFuture diff --git a/src/main/scala/viper/server/DataProtocol.scala b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala similarity index 95% rename from src/main/scala/viper/server/DataProtocol.scala rename to src/main/scala/viper/server/frontends/lsp/DataProtocol.scala index 8823198..b6e2b37 100644 --- a/src/main/scala/viper/server/DataProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import org.eclipse.lsp4j.{Diagnostic, Position, Range} @@ -11,7 +19,7 @@ object VerificationSuccess extends Enumeration { val Error = Value // Caused by veification taking too long val Timeout = Value } -import viper.server.VerificationSuccess._ +import viper.server.frontends.lsp.VerificationSuccess._ object VerificationState extends Enumeration { type VerificationState = Value @@ -23,14 +31,13 @@ object VerificationState extends Enumeration { val Stopping = Value val Stage = Value } -import viper.server.VerificationState._ object SettingsErrorType extends Enumeration { type SettingsErrorType = Value val Error, Warning = Value } -import viper.server.SettingsErrorType._ +import viper.server.frontends.lsp.SettingsErrorType._ object LogLevel extends Enumeration { type LogLevel = Value diff --git a/src/main/scala/viper/server/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala similarity index 96% rename from src/main/scala/viper/server/FileManager.scala rename to src/main/scala/viper/server/frontends/lsp/FileManager.scala index afa95a0..59af933 100644 --- a/src/main/scala/viper/server/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.net.URI import java.nio.file.{Path, Paths} @@ -6,8 +14,9 @@ import java.util.concurrent.{CompletableFuture => CFuture} import akka.actor.{Actor, Props} import org.eclipse.lsp4j.{Diagnostic, DiagnosticSeverity, Location, Position, PublishDiagnosticsParams, Range, SymbolInformation, SymbolKind} -import viper.server.VerificationState._ -import viper.server.VerificationSuccess._ +import viper.server.frontends.lsp +import viper.server.frontends.lsp.VerificationState._ +import viper.server.frontends.lsp.VerificationSuccess._ import viper.silver.ast.{Domain, Field, Function, Method, Predicate, SourcePosition} import viper.silver.reporter._ @@ -40,7 +49,7 @@ class FileManager(file_uri: String) { var progress: Progress = _ // var shownExecutionTrace: Array[ExecutionTrace] = _ var symbolInformation: ArrayBuffer[SymbolInformation] = ArrayBuffer() - var definitions: ArrayBuffer[Definition] = ArrayBuffer() + var definitions: ArrayBuffer[lsp.Definition] = ArrayBuffer() //working variables private var lines: Array[String] = Array() @@ -204,7 +213,7 @@ class FileManager(file_uri: String) { } val sourcePos = d.location.asInstanceOf[viper.silver.ast.SourcePosition] val location: Position = new Position(sourcePos.start.line, sourcePos.start.column) - val definition: Definition = Definition(d.typ, d.name, location, range) + val definition: lsp.Definition = lsp.Definition(d.typ, d.name, location, range) definitions.append(definition) }) case StatisticsReport(m, f, p, _, _) => diff --git a/src/main/scala/viper/server/IdeLanguageClient.scala b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala similarity index 84% rename from src/main/scala/viper/server/IdeLanguageClient.scala rename to src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala index 0de5bdc..a89d7ac 100644 --- a/src/main/scala/viper/server/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.util.concurrent.CompletableFuture diff --git a/src/main/scala/viper/server/IdeLog.scala b/src/main/scala/viper/server/frontends/lsp/IdeLog.scala similarity index 83% rename from src/main/scala/viper/server/IdeLog.scala rename to src/main/scala/viper/server/frontends/lsp/IdeLog.scala index b8ad8c2..f791213 100644 --- a/src/main/scala/viper/server/IdeLog.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLog.scala @@ -1,6 +1,14 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ -import viper.server.LogLevel._ +package viper.server.frontends.lsp + +import viper.server.frontends.lsp.LogLevel._ object Log { def log(message: String, logLevel: LogLevel) = { diff --git a/src/main/scala/viper/server/Progress.scala b/src/main/scala/viper/server/frontends/lsp/Progress.scala similarity index 92% rename from src/main/scala/viper/server/Progress.scala rename to src/main/scala/viper/server/frontends/lsp/Progress.scala index ea6b830..02a95db 100644 --- a/src/main/scala/viper/server/Progress.scala +++ b/src/main/scala/viper/server/frontends/lsp/Progress.scala @@ -3,10 +3,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * - * Copyright (c) 2011-2019 ETH Zurich. + * Copyright (c) 2011-2020 ETH Zurich. */ -package viper.server +package viper.server.frontends.lsp class Progress(val nofPredicates: Int, val nofFunctions: Int, val nofMethods: Int) { diff --git a/src/main/scala/viper/server/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala similarity index 92% rename from src/main/scala/viper/server/Receiver.scala rename to src/main/scala/viper/server/frontends/lsp/Receiver.scala index 99a3e47..1b480da 100644 --- a/src/main/scala/viper/server/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -1,16 +1,25 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.util.concurrent.{CompletableFuture => CFuture} import org.eclipse.lsp4j.jsonrpc.services.{JsonNotification, JsonRequest} import org.eclipse.lsp4j.services.{LanguageClient, LanguageClientAware} -import org.eclipse.lsp4j.{CompletionItem, CompletionItemKind, CompletionList, CompletionParams, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} -import viper.server.LogLevel._ -import viper.server.VerificationState._ +import org.eclipse.lsp4j.{DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, InitializeParams, InitializeResult, InitializedParams, Location, Range, ServerCapabilities, SymbolInformation, TextDocumentPositionParams, TextDocumentSyncKind} +import viper.server.frontends.lsp.LogLevel._ +import viper.server.frontends.lsp.VerificationState._ -import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer + + abstract class StandardReceiver extends LanguageClientAware { var received_shutdown = false @@ -30,6 +39,10 @@ abstract class StandardReceiver extends LanguageClientAware { capabilities.setDocumentSymbolProvider(true) CFuture.completedFuture(new InitializeResult(capabilities)) } + @JsonNotification("initialized") + def initialize(params: InitializedParams): Unit = { + println("initialized") + } @JsonNotification("textDocument/didOpen") def onDidOpenDocument(params: DidOpenTextDocumentParams): Unit = { diff --git a/src/main/scala/viper/server/Settings.scala b/src/main/scala/viper/server/frontends/lsp/Settings.scala similarity index 99% rename from src/main/scala/viper/server/Settings.scala rename to src/main/scala/viper/server/frontends/lsp/Settings.scala index f47d3b1..bb7365d 100644 --- a/src/main/scala/viper/server/Settings.scala +++ b/src/main/scala/viper/server/frontends/lsp/Settings.scala @@ -1,4 +1,12 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.util.concurrent.{CompletableFuture => CFuture} diff --git a/src/main/scala/viper/server/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala similarity index 94% rename from src/main/scala/viper/server/ViperServerService.scala rename to src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index b9c25de..0e953ba 100644 --- a/src/main/scala/viper/server/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -1,11 +1,18 @@ -package viper.server +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +package viper.server.frontends.lsp import java.util.concurrent.{CompletableFuture => CFuture} import akka.actor.{PoisonPill, Props} import akka.pattern.ask import akka.util.Timeout -import viper.server.VerificationState._ import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} import viper.server.core.{VerificationJobHandler, ViperCache, ViperCoreServer} import viper.server.protocol.ViperServerProtocol.Stop From b129773a57fd695df27df4f4a2373b2897b47b6d Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 15:51:57 +0200 Subject: [PATCH 08/79] Added VSI --- src/main/scala/viper/server/vsi/Cache.scala | 288 +++++++++++++++ .../scala/viper/server/vsi/Envelope.scala | 31 ++ src/main/scala/viper/server/vsi/HTTP.scala | 159 ++++++++ .../scala/viper/server/vsi/Protocol.scala | 36 ++ .../viper/server/vsi/VerificationServer.scala | 344 ++++++++++++++++++ 5 files changed, 858 insertions(+) create mode 100644 src/main/scala/viper/server/vsi/Cache.scala create mode 100644 src/main/scala/viper/server/vsi/Envelope.scala create mode 100644 src/main/scala/viper/server/vsi/HTTP.scala create mode 100644 src/main/scala/viper/server/vsi/Protocol.scala create mode 100644 src/main/scala/viper/server/vsi/VerificationServer.scala diff --git a/src/main/scala/viper/server/vsi/Cache.scala b/src/main/scala/viper/server/vsi/Cache.scala new file mode 100644 index 0000000..93a01a0 --- /dev/null +++ b/src/main/scala/viper/server/vsi/Cache.scala @@ -0,0 +1,288 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server.vsi + +import viper.silver.utility.CacheHelper + +import scala.collection.mutable.{ListBuffer, Map => MutableMap} + +/** The goal of this generic caching trait is to provide: + * + * A) a storing system that can decide whether or not stored resources are valid. + * + * in order to implement: + * + * B) A simple caching API + * + * A verification server should - very bluntly expressed - take a program and return a + * (potentially empty) set of errors. This process is assumed to be deterministic. I.e., + * verifying the same program twice results in the exact same verification result. Of course, + * changing the program is likely to change the verification outcome. However, it may well be + * that the outcome will not completely change. E.g., if a program consists of two totally + * independent methods, changing the first may have absolutely no effect on the verification + * results of the second. In such a situation, resources can be economized by storing the + * results, reverifying only the first method and not reverifying the second. This requires a + * storage system that can: + * + * 1) store verification results for individual program members + * 2) decide whether or not stored results are still valid if the program is changed. + * + * Once such a system is in place it can be used to implement a simple API. This API should take + * a program P, analyse it, transforms it and return a program P' as well as a set of + * verification results. This program should thereby have been transformed in such a way that a + * verifier may not perform unnecessary verifications on its members. + * */ +abstract class Cache { + + override def toString: String = _cache.toString + + /** The cache infrastructure is a nested, mutable Map. The maps can be described in terms of + * their key and values as follows: (FileName -> (hashString -> cacheEntries)). + * + * The inner map is referred to as the fileCache. As the name indicates, it stores, for each + * file, a number of hashes and corresponding cache entries. + */ + protected val _cache = MutableMap[String, FileCash]() + type FileCash = MutableMap[String, List[CacheEntry]] + + protected val _program_cache = MutableMap[Ast, MutableMap[CacheableMember, List[Member]]]() + + /** This method transforms a program and returns verification results based on the cache's + * current state. + * + * The input and out put program should be equivalent with respect to verification, but they + * might differ in their AST. In particular, the output program should will be transformed in + * such a way that verifying it is faster than verifying the input program. + * + * The method also returns previously cached verification result for members of the AST that + * hit the cache. + * */ + def retrieve( + file_key: String, + input_prog: Ast): (Ast, List[CacheEntry]) = { + + val cache_entries: ListBuffer[CacheEntry] = ListBuffer() + val concerningsToCache: ListBuffer[CacheableMember] = ListBuffer() + val concerningsToVerify: ListBuffer[CacheableMember] = ListBuffer() + + //read errors from cache + val cachable_members = input_prog.decompose() + + val prog_dependencies = _program_cache.find({ + case (k, _) => k.equals(input_prog) + }) + + prog_dependencies match { + case Some((_, dep_map)) => + cachable_members.foreach(cm => { + val dependencies = dep_map(cm) + get(file_key, cm, dependencies) match { + case Some(matched_entry) => + concerningsToCache += cm.transform + cache_entries += matched_entry + case None => + //Nothing in cache, request verification + concerningsToVerify += cm + } + }) + case None => + val dep_map = MutableMap[CacheableMember, List[Member]]() + cachable_members.foreach(cm => { + val dependencies = cm.getDependencies(input_prog) + dep_map += (cm -> dependencies) + get(file_key, cm, dependencies) match { + case Some(matched_entry) => + concerningsToCache += cm.transform + cache_entries += matched_entry + case None => + //Nothing in cache, request verification + concerningsToVerify += cm + } + }) + _program_cache += (input_prog -> dep_map) + } + + val all_concernings: List[CacheableMember] = concerningsToCache.toList ++ concerningsToVerify.toList + val output_prog: Ast = input_prog.compose(all_concernings) + (output_prog, cache_entries.toList) + } + + /** Utility function to retrieve entries for single members. + * */ + final def get( + file_key: String, + key: CacheableMember, + dependencies: List[Member]): Option[CacheEntry] = { + + val concerning_hash = key.hash + val dependencies_hash = dependencies.map(_.hash).mkString(" ") + val dependency_hash = CacheHelper.buildHash(concerning_hash + dependencies_hash) + assert(concerning_hash != null) + + for { + fileCache <- _cache.get(file_key) + cacheEntries <- fileCache.get(concerning_hash) + validEntry <- cacheEntries.find(_.depency_hash == dependency_hash) + } yield validEntry + } + + /** This function updates the cache's state by adding/updating entries. + * + * All cacheable member that were (re-)verified in this verification attempt must be cached. + * This obviously requires the cacheable member itself as well as the content that's to be + * stored. + * + * Additionally, a list of dependencies must be passed. This list is used to compute the + * dependency hash, which is used to determine the validity of a cache entry. + * */ + def update( + file_key: String, + key: CacheableMember, + dependencies: List[Member], + content: CacheContent): List[CacheEntry] = { + + val concerning_hash = key.hash + val dependencies_hash = dependencies.map(_.hash).mkString(" ") + val dependency_hash = CacheHelper.buildHash(concerning_hash + dependencies_hash) + val new_entry: CacheEntry = new CacheEntry(key, content, dependency_hash) + + assert(concerning_hash != null) + + _cache.get(file_key) match { + case Some(fileCache) => + val existing_entries = fileCache.getOrElse(concerning_hash, Nil) + val updated_cacheEntries = new_entry :: existing_entries + + fileCache(concerning_hash) = updated_cacheEntries + updated_cacheEntries + case None => + //if file not in cache yet, create new map entry for it. Recall the function. + _cache += (file_key -> collection.mutable.Map[String, List[CacheEntry]]()) + update(file_key, key, dependencies, content) + } + } + + /** Resets the cache for a particular file. + * */ + def forgetFile(file_key: String): Option[String] = { + _cache.remove(file_key) match { + case Some(_) => Some(file_key) + case None => None + } + } + + /** Resets the entire cache. + * */ + def resetCache(): Unit = { + _cache.clear() + } +} + + +/** This case class represents a cache entry. + * + * An must contain three pieces of information: + * + * - Which cacheable AST member the entry is for. + * - What the cache will store for that cacheable member. + * - A dependency hash for that cacheable member at the time it is stored. + * + * The dependency hash reflects the state of the dependencies at the time when the entry was + * created. If, at some point, a members dependency hash is no longer equal to the one stored + * here, the entry is no longer valid. + * */ +case class CacheEntry(concerning: CacheableMember, content: CacheContent, depency_hash: String) + + +// ===== AUXILIARY TRAITS ================================================================== + + +/** This trait is a generic wrapper for cache content. It must be extended in order to specify + * what the cache will hold. + * + * Typically, this will hold client-specific verification results. + * */ +trait CacheContent + + +/** This trait is a generic wrapper for an AST Members. It must be extended in order to specify + * what members an AST is made of. + * */ +trait Member { + + /** Must be implemented to return a hash String for this Member. + * */ + def hash(): String +} + + +/** This trait is a generic wrapper for the subset of AST Members that are cacheable. It must be + * extended in order to specify which AST members are cacheable. + * + * Some languages for which caching is implemented in a verification server might not provide + * caching for each of its members. In Viper, for example, only members of type Methods are + * cacheable. + * + * A cacheable member, contrary to a normal member, must provide additional functionality. This + * comes in the form of the (abstract) methods transform and getDependencies. + * */ +trait CacheableMember extends Member { + + /** Must be implemented to transform this CacheableMember such that it is not reverified with + * the backends used. + * + * A member for which verification results have been retrieved from the cache should not be + * reverified. For this, a member must be transformed. The transformation should be done such + * that it allows the verifier to distinguish between members that hit the cache (and do not + * need verification) and members that missed the cache (and therefore do need reverification). + * */ + def transform: CacheableMember + + /** Must be implemented to return a list of members which this CacheableMember depends on, in + * terms of verification. + * + * A member may hit the cache, but the attached verification results might be invalid. Reason + * for this is that other members in the program have might have changed in such a way that + * they influenced this member's verification outcome. These influencing members are called + * dependencies and need to be checked when retrieving a member's attached result from cache. + * */ + def getDependencies(ast: Ast): List[Member] +} + + +/** This trait is a generic wrapper for an AST. + * */ +trait Ast { + + /** Must be implemented to return and Ast given a list of transformed CacheableMembers and this + * Ast. + * + * Potentially, only a subset of Member in an AST will be cacheable. In order for the cache to + * know which these are, an Ast must implement a decompose method. + * */ + def compose(cs: List[CacheableMember]): Ast + + /** Must be implemented to return a list of CacheableMembers for this Ast. + * + * * After content has been retrieved from the cache, the corresponding members must be + * transformed, so as to not be reverified. These members must be reintroduced into the AST, + * which is done by implementing the compose method. + * */ + def decompose(): List[CacheableMember] + + /** Must be implemented to decide whether an Ast is equal to this Ast in terms of verification. + * + * If it is known that a program is equal to one previously cached, the results should be + * retrievable without delay. In particular, it should not be necessary to compute the + * dependency list for each CacheableMembers when retrieving their results from the cache, as + * equal programs imply equal dependency lists. + * + * Implementing this method in a non-trivial way is obviously difficult. Clients who do not + * want to bother with this feature can simply return false for any pair of programs. + * */ + def equals(other: Ast): Boolean +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/Envelope.scala b/src/main/scala/viper/server/vsi/Envelope.scala new file mode 100644 index 0000000..4121b3c --- /dev/null +++ b/src/main/scala/viper/server/vsi/Envelope.scala @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server.vsi + + +/** This trait is a generic wrapper for Messages generated by backends. It must be extended in + * order to specify what types of messages the client uses. + * */ +trait Envelope + +sealed trait Post { + type A +} + +trait Packer extends Post { + + /** Must be implemented to map client-specific messages to Envelopes. + * */ + def pack(m: A): Envelope +} + +trait Unpacker extends Post { + + /** Must be implemented to map Envelopes to client-specific messages. + * */ + def unpack(m: Envelope): A +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala new file mode 100644 index 0000000..3f7f2bf --- /dev/null +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -0,0 +1,159 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server.vsi + +import akka.NotUsed +import akka.actor.PoisonPill +import akka.http.scaladsl.Http +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ +import akka.http.scaladsl.marshalling.ToResponseMarshallable +import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server.Route +import akka.pattern.ask +import akka.stream.scaladsl.Source +import akka.util.Timeout +import viper.server.vsi.VerificationProtocol.Stop + +import scala.concurrent.Future +import scala.concurrent.duration._ +import scala.util.{Failure, Success, Try} + +/** This trait contains the bear essentials required for an HTTP server. + * */ +sealed trait BasicHttp { + + var port: Int = _ + + /** Must be implemented to return the routes defined for this server. + * */ + def routes(): Route +} + +/** This trait provides the functionality to add additional routes to an HTTP Server. + * */ +sealed trait CustomizableHttp extends BasicHttp { + + /** This method merges two given routes into one. + * */ + def addRoute(existingRoute: Route, newRoute: Route): Route = { + existingRoute ~ newRoute + } +} + +/** This trait extends a VerificationServer by providing common HTTP functionality. + * + * Examples for such functionality are: + * + * - Stopping a VerifiationServer. + * - Submitting verification requests. + * - Requesting results for verification requests. + * + * The VerificationServer-specific functionality will need to be implemented for each individual + * server. In particular, this means providing a protocol that returns the VerificationServer's + * responses as type [[ToResponseMarshallable]]. + * */ +trait VerificationServerHTTP extends VerificationServer with CustomizableHttp { + + def setRoutes(): Route + + var bindingFuture: Future[Http.ServerBinding] = _ + + override def start(active_jobs: Int): Unit = { + jobs = new JobPool(active_jobs) + bindingFuture = Http().bindAndHandle(setRoutes, "localhost", port) + _termActor = system.actorOf(Terminator.props(bindingFuture), "terminator") + isRunning = true + } + + /** Implement VerificationServer- specific handling of server shutdown. + * (Response should depend on interruption state.) + * */ + def serverStopConfirmation(interruption_future: Try[List[String]]): ToResponseMarshallable + + /** Implement VerificationServer- specific handling of VerificationRequests. + * */ + def onVerifyPost(vr: Requests.VerificationRequest): ToResponseMarshallable + + /** Implement VerificationServer- specific handling of a request for streaming results. + * */ + def unpackMessages(s: Source[Envelope, NotUsed]): ToResponseMarshallable + + /** Implement VerificationServer- specific handling of a failed request for streaming results. + * */ + def verificationRequestRejection(jid: Int, e: Throwable): ToResponseMarshallable + + /** Implement VerificationServer- specific handling of a successful request for discarding a job. + * */ + def discardJobConfirmation(jid: Int, msg: String): ToResponseMarshallable + + /** Implement VerificationServer- specific handling of a failed request for for discarding a job. + * */ + def discardJobRejection(jid: Int): ToResponseMarshallable + + override final def routes(): Route = { + /** Send GET request to "/exit". + */ + path("exit") { + get { + onComplete(getInterruptFutureList()) { err: Try[List[String]] => + _termActor ! Terminator.Exit + complete( serverStopConfirmation(err) ) + } + } + } + } ~ path("verify") { + /** Send POST request to "/verify". + */ + post { + entity(as[Requests.VerificationRequest]) { r => + complete(onVerifyPost(r)) + } + } + } ~ path("verify" / IntNumber) { jid => + /** Send GET request to "/verify/" where is a non-negative integer. + * must be an ID of an existing verification job. + */ + get { + jobs.lookupJob(JobID(jid)) match { + case Some(handle_future) => + // Found a job with this jid. + onComplete(handle_future) { + case Success(handle) => + val s: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) + _termActor ! Terminator.WatchJobQueue(JobID(jid), handle) + complete(unpackMessages(s)) + case Failure(error) => + complete(verificationRequestRejection(jid, error)) + } + case _ => + complete(verificationRequestRejection(jid, JobNotFoundException())) + } + } + } ~ path("discard" / IntNumber) { jid => + /** Send GET request to "/discard/" where is a non-negative integer. + * must be an ID of an existing verification job. + */ + get { + jobs.lookupJob(JobID(jid)) match { + case Some(handle_future) => + onComplete(handle_future) { + case Success(handle) => + implicit val askTimeout: Timeout = Timeout(5000 milliseconds) + val interrupt_done: Future[String] = (handle.job_actor ? Stop).mapTo[String] + onSuccess(interrupt_done) { msg => + handle.job_actor ! PoisonPill // the actor played its part. + complete(discardJobConfirmation(jid, msg)) + } + case Failure(_) => + complete(discardJobRejection(jid)) + } + case _ => + complete(discardJobRejection(jid)) + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/Protocol.scala b/src/main/scala/viper/server/vsi/Protocol.scala new file mode 100644 index 0000000..0541a0f --- /dev/null +++ b/src/main/scala/viper/server/vsi/Protocol.scala @@ -0,0 +1,36 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server.vsi + +import akka.stream.scaladsl.SourceQueueWithComplete +import org.reactivestreams.Publisher +import spray.json.{DefaultJsonProtocol, RootJsonFormat} + +// Protocol to communicate with QueueActor +object TaskProtocol { + case class BackendReport(msg: Envelope) + case class FinalBackendReport(success: Boolean) +} + +// Protocol to start/stop verification process. +object VerificationProtocol { + + // Request Job Actor to execute verification task + case class Verify(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]) + + // Verification interrupt request to Terminator Actor + case class Stop() +} + +object Requests extends akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport with DefaultJsonProtocol { + + case class VerificationRequest(arg: String) + implicit val VerificationRequest_format: RootJsonFormat[VerificationRequest] = jsonFormat1(VerificationRequest.apply) + + case class CacheResetRequest(backend: String, file: String) + implicit val CacheResetRequest_format: RootJsonFormat[CacheResetRequest] = jsonFormat2(CacheResetRequest.apply) +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala new file mode 100644 index 0000000..28d6327 --- /dev/null +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -0,0 +1,344 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server.vsi + +import akka.actor.{Actor, ActorRef, ActorSystem, PoisonPill, Props} +import akka.http.scaladsl.Http +import akka.pattern.ask +import akka.stream.scaladsl.{Keep, Sink, Source, SourceQueueWithComplete} +import akka.stream.{ActorMaterializer, OverflowStrategy, QueueOfferResult} +import akka.util.Timeout +import akka.{Done, NotUsed} +import org.reactivestreams.Publisher + +import scala.collection.mutable +import scala.concurrent.duration._ +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + + +class VerificationServerException extends Exception +case class JobNotFoundException() extends VerificationServerException + +case class JobID(id: Int) +case class JobHandle(job_actor: ActorRef, + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope]) + +/** This class manages the verification jobs the server receives. + */ +class JobPool(val MAX_ACTIVE_JOBS: Int = 3) { + var jobHandles: mutable.Map[Int, Future[JobHandle]] = mutable.Map[Int, Future[JobHandle]]() + private var _nextJobId: Int = 0 + + def newJobsAllowed = jobHandles.size < MAX_ACTIVE_JOBS + + /** Creates a Future of a JobHandle. + * + * For the next available job ID the function job_executor will set up a JobActor. That actor + * will start a verification process and produce a Future JobHandle. The Future will + * successfully complete as soon as the verification process was started successfully. + * */ + def bookNewJob(job_executor: Int => Future[JobHandle]): (Int, Future[JobHandle]) = { + val new_jid = _nextJobId + jobHandles(new_jid) = job_executor(new_jid) + _nextJobId = _nextJobId + 1 + (new_jid, jobHandles(new_jid)) + } + + /** Discards the JobHandle for the given JobID + * */ + def discardJob(jid: JobID): mutable.Map[Int, Future[JobHandle]] = { + jobHandles -= jid.id + } + + def lookupJob(jid: JobID): Option[ Future[JobHandle] ] = { + jobHandles.get(jid.id) + } +} + +/** This trait provides state and process management functionality for verification servers. + * + * The server runs on Akka's actor system. This means that the entire server's state + * and process management are run by actors. The 3 actors in charge are: + * + * 1) Job Actor + * 2) Queue Actor + * 3) Terminator Actor + * + * The first two actors manage individual verification processes. I.e., on + * initializeVerificationProcess() and instance of each actor is created. The JobActor launches + * the actual VerificationTask, while the QueueActor acts as a middleman for communication + * between a VerificationTask's backend and the server. The Terminator Actor is in charge of + * terminating both individual processes and the server. + */ +trait VerificationServer extends Unpacker { + + implicit val system: ActorSystem = ActorSystem("Main") + implicit val executionContext = ExecutionContext.global + implicit val materializer: ActorMaterializer = ActorMaterializer() + + protected var jobs: JobPool = _ + var isRunning: Boolean = false + + /** Configures an instance of ViperCoreServer. + * + * This function must be called before any other. Calling any other function before this one + * will result in an IllegalStateException. + * */ + def start(active_jobs: Int): Unit = { + jobs = new JobPool(active_jobs) + _termActor = system.actorOf(Terminator.props(), "terminator") + isRunning = true + } + + // --- Actor: Terminator --- + + protected var _termActor: ActorRef = _ + + object Terminator { + case object Exit + case class WatchJobQueue(jid: JobID, handle: JobHandle) + + def props(bindingFuture: Future[Http.ServerBinding]): Props = Props(new Terminator(Some(bindingFuture))) + def props(): Props = Props(new Terminator(None)) + } + + class Terminator(bindingFuture: Option[Future[Http.ServerBinding]]) extends Actor { + + override def receive: PartialFunction[Any, Unit] = { + case Terminator.Exit => + bindingFuture match { + case Some(future) => + future + .flatMap(_.unbind()) // trigger unbinding from the port + .onComplete(_ => { + system.terminate() // and shutdown when done + }) + case None => + system.terminate() // shutdown + } + case Terminator.WatchJobQueue(jid, handle) => + val queue_completion_future: Future[Done] = handle.queue.watchCompletion() + queue_completion_future.onComplete( { + case Failure(e) => + throw e + case Success(_) => + jobs.discardJob(jid) + }) + } + } + + + // --- Actor: JobActor --- + + object JobActor { + def props(id: Int): Props = Props(new JobActor(id)) + } + + class JobActor(private val id: Int) extends Actor { + + private var _verificationTask: Thread = _ + + private def interrupt: Boolean = { + if (_verificationTask != null && _verificationTask.isAlive) { + _verificationTask.interrupt() + _verificationTask.join() + return true + } + false + } + + private def resetVerificationTask(): Unit = { + if (_verificationTask != null && _verificationTask.isAlive) { + _verificationTask.interrupt() + _verificationTask.join() + } + _verificationTask = null + } + + override def receive: PartialFunction[Any, Unit] = { + case VerificationProtocol.Stop => + val did_I_interrupt = interrupt + if (did_I_interrupt) { + sender ! s"Job #$id has been successfully interrupted." + } else { + sender ! s"Job #$id has already been finalized." + } + case VerificationProtocol.Verify(task, queue, publisher) => + resetVerificationTask() + sender ! startJob(task, queue, publisher) + case msg => + throw new Exception("Main Actor: unexpected message received: " + msg) + } + + private def startJob(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]): JobHandle = { + _verificationTask = task + _verificationTask.start() + JobHandle(self, queue, publisher) + } + } + + + // --- Actor: MessageActor --- + + object QueueActor { + def props(jid: Int, queue: SourceQueueWithComplete[Envelope]): Props = + Props(new QueueActor(jid, queue)) + } + + class QueueActor(jid: Int, queue: SourceQueueWithComplete[Envelope]) extends Actor { + + override def receive: PartialFunction[Any, Unit] = { + case TaskProtocol.BackendReport(msg) => + val offer_status = queue.offer(msg) + sender() ! offer_status + case TaskProtocol.FinalBackendReport(_) => + queue.complete() + self ! PoisonPill + case _ => + } + } + + /** This method starts a verification process. + * + * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. + */ + protected def initializeVerificationProcess(task:VerificationTask): JobID = { + if(!isRunning) { + throw new IllegalStateException("Instance of ViperCoreServer already stopped") + } + + if (jobs.newJobsAllowed) { + def createJob(new_jid: Int): Future[JobHandle] = { + + implicit val askTimeout: Timeout = Timeout(5000 milliseconds) + val job_actor = system.actorOf(JobActor.props(new_jid), s"job_actor_$new_jid") + val (queue, publisher) = Source.queue[Envelope](10000, OverflowStrategy.backpressure) + .toMat(Sink.asPublisher(false))(Keep.both) + .run() + val message_actor = system.actorOf(QueueActor.props(new_jid, queue), s"queue_actor_$new_jid") + task.setQueueActor(message_actor) + val task_with_actor = new Thread(task) + val answer = job_actor ? VerificationProtocol.Verify(task_with_actor, queue, publisher) + val new_job_handle: Future[JobHandle] = answer.mapTo[JobHandle] + new_job_handle + } + val (id, _) = jobs.bookNewJob(createJob) + JobID(id) + } else { + JobID(-1) // Process Management running at max capacity. + } + } + + /** Stream all messages generated by the backend to some actor. + * + * Deletes the JobHandle on completion. + */ + protected def streamMessages(jid: JobID, clientActor: ActorRef): Option[Future[Unit]] = { + if(!isRunning) { + throw new IllegalStateException("Instance of ViperCoreServer already stopped") + } + + jobs.lookupJob(jid) match { + case Some(handle_future) => + def mapHandle(handle: JobHandle): Future[Unit] = { + val src_envelope: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) + val src_msg: Source[A , NotUsed] = src_envelope.map(e => unpack(e)) + src_msg.runWith(Sink.actorRef(clientActor, Success)) + _termActor ! Terminator.WatchJobQueue(jid, handle) + handle.queue.watchCompletion().map(_ => ()) + } + Some(handle_future.flatMap(mapHandle)) + case None => None + } + } + + /** Stops an instance of ViperCoreServer from running. + * + * As such it should be the last method called. Calling any other function after stop will + * result in an IllegalStateException. + * */ + def stop(): Unit = { + if(!isRunning) { + throw new IllegalStateException("Instance of ViperCoreServer already stopped") + } + isRunning = false + + getInterruptFutureList() onComplete { + case Success(_) => + _termActor ! Terminator.Exit + println(s"shutting down...") + case Failure(err_msg) => + _termActor ! Terminator.Exit + println(s"forcibly shutting down...") + } + } + + /** This method interrupts active jobs upon termination of the server. + */ + protected def getInterruptFutureList(): Future[List[String]] = { + val interrupt_future_list: List[Future[String]] = jobs.jobHandles map { case (jid, handle_future) => + handle_future.flatMap { + case JobHandle(actor, _, _) => + implicit val askTimeout: Timeout = Timeout(1000 milliseconds) + (actor ? VerificationProtocol.Stop).mapTo[String] + } + } toList + val overall_interrupt_future: Future[List[String]] = Future.sequence(interrupt_future_list) + overall_interrupt_future + } +} + + +/** This class is a generic wrapper for a any sort of verification a VerificationServer might + * work on. + * + * It has the following properties: + * - implements runnable + * - provides a reference to a queue actor. + * + * The first serves the purpose of running the process concurrently. The second allows to + * communicate from the verification process to the server. + * */ +abstract class VerificationTask()(implicit val executionContext: ExecutionContext) extends Runnable with Packer { + + private var q_actor: ActorRef = _ + + final def setQueueActor(actor: ActorRef): Unit = { + q_actor = actor + } + + /** Sends massage to the attached actor. + * + * The actor receiving this message offers it to a queue. This offering returns a Future, + * which will eventually indicate whether or not the offer was successful. This method is + * blocking, as it waits for the successful completion of such an offer. + * */ + protected def enqueueMessages(msg: A): Unit = { + implicit val askTimeout: Timeout = Timeout(5000 milliseconds) + + var current_offer: Future[QueueOfferResult] = null + val answer = q_actor ? TaskProtocol.BackendReport(pack(msg)) + current_offer = answer.flatMap({ + case res: Future[QueueOfferResult] => res + }) + while(current_offer == null || !current_offer.isCompleted){ + Thread.sleep(10) + } + } + + /** Notify the queue actor that the task has come to an end + * + * The actor receiving this message will close the queue. + * + * @param success indicates whether or not the task has ended as successfully. + * */ + protected def registerTaskEnd(success: Boolean): Unit = { + q_actor ! TaskProtocol.FinalBackendReport(success) + } +} From 887f2aa22b1111a0662a9c0335f0b375ac80599c Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 16:11:13 +0200 Subject: [PATCH 09/79] preparing merge from master --- .../viper/server/{ => frontends/http}/ViperHttpServer.scala | 4 ++-- .../http/jsonWriters}/AlloySolutionWriter.scala | 2 +- .../http/jsonWriters}/SymbExLogReportWriter.scala | 2 +- .../{writer => frontends/http/jsonWriters}/TermWriter.scala | 2 +- .../http/jsonWriters}/ViperIDEProtocol.scala | 3 +-- 5 files changed, 6 insertions(+), 7 deletions(-) rename src/main/scala/viper/server/{ => frontends/http}/ViperHttpServer.scala (99%) rename src/main/scala/viper/server/{writer => frontends/http/jsonWriters}/AlloySolutionWriter.scala (96%) rename src/main/scala/viper/server/{writer => frontends/http/jsonWriters}/SymbExLogReportWriter.scala (99%) rename src/main/scala/viper/server/{writer => frontends/http/jsonWriters}/TermWriter.scala (99%) rename src/main/scala/viper/server/{protocol => frontends/http/jsonWriters}/ViperIDEProtocol.scala (99%) diff --git a/src/main/scala/viper/server/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala similarity index 99% rename from src/main/scala/viper/server/ViperHttpServer.scala rename to src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index e36300b..cd2344e 100644 --- a/src/main/scala/viper/server/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -6,7 +6,7 @@ * Copyright (c) 2011-2019 ETH Zurich. */ -package viper.server +package viper.server.frontends.http import akka.NotUsed import akka.actor.PoisonPill @@ -20,7 +20,7 @@ import edu.mit.csail.sdg.parser.CompUtil import edu.mit.csail.sdg.translator.{A4Options, TranslateAlloyToKodkod} import viper.server.ViperRequests.{AlloyGenerationRequest, CacheResetRequest, VerificationRequest} import viper.server.core.{VerificationJobHandler, ViperCache, ViperCoreServer} -import viper.server.protocol.ViperIDEProtocol._ +import viper.server.frontends.http.jsonWriters.ViperIDEProtocol._ import viper.server.protocol.ViperServerProtocol import viper.server.protocol.ViperServerProtocol._ import viper.server.utility.AstGenerator diff --git a/src/main/scala/viper/server/writer/AlloySolutionWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala similarity index 96% rename from src/main/scala/viper/server/writer/AlloySolutionWriter.scala rename to src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala index 1aefb35..88d6091 100644 --- a/src/main/scala/viper/server/writer/AlloySolutionWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala @@ -1,4 +1,4 @@ -package viper.server.writer +package viper.server.frontends.http.jsonWriters import edu.mit.csail.sdg.ast.Sig.Field import edu.mit.csail.sdg.ast.{ExprVar, Sig} diff --git a/src/main/scala/viper/server/writer/SymbExLogReportWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala similarity index 99% rename from src/main/scala/viper/server/writer/SymbExLogReportWriter.scala rename to src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala index 834ded7..5546984 100644 --- a/src/main/scala/viper/server/writer/SymbExLogReportWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala @@ -1,4 +1,4 @@ -package viper.server.writer +package viper.server.frontends.http.jsonWriters import spray.json.{JsArray, JsField, JsNull, JsObject, JsString, JsValue} import viper.silicon.interfaces.state.Chunk diff --git a/src/main/scala/viper/server/writer/TermWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala similarity index 99% rename from src/main/scala/viper/server/writer/TermWriter.scala rename to src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala index 0bb03e5..194821a 100644 --- a/src/main/scala/viper/server/writer/TermWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala @@ -1,4 +1,4 @@ -package viper.server.writer +package viper.server.frontends.http.jsonWriters import spray.json.{JsArray, JsNull, JsObject, JsString, JsValue} import viper.silicon.state.Identifier diff --git a/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala similarity index 99% rename from src/main/scala/viper/server/protocol/ViperIDEProtocol.scala rename to src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index 80b24d8..c260260 100644 --- a/src/main/scala/viper/server/protocol/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -1,4 +1,4 @@ -package viper.server.protocol +package viper.server.frontends.http.jsonWriters import akka.NotUsed import akka.http.scaladsl.common.{EntityStreamingSupport, JsonEntityStreamingSupport} @@ -6,7 +6,6 @@ import akka.stream.scaladsl.Flow import akka.util.ByteString import edu.mit.csail.sdg.translator.A4Solution import spray.json.DefaultJsonProtocol -import viper.server.writer.{AlloySolutionWriter, SymbExLogReportWriter, TermWriter} import viper.silicon.SymbLog import viper.silicon.state.terms.Term import viper.silver.reporter._ From 64f67fc1f8ae10bbf621f43004f23e1fb01c0105 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 17:54:39 +0200 Subject: [PATCH 10/79] implemented Language Server with updates from master --- .vscode/settings.json | 3 -- .../jsonWriters/AlloySolutionWriter.scala | 1 - .../server/frontends/lsp/FileManager.scala | 19 +++++----- .../frontends/lsp/ViperServerService.scala | 36 +++++++++---------- .../viper/server/vsi/VerificationServer.scala | 6 +++- 5 files changed, 34 insertions(+), 31 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index f9a5742..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.fontSize": 12 -} \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala index 6eec3a3..28499c2 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala @@ -5,7 +5,6 @@ import edu.mit.csail.sdg.ast.{ExprVar, Sig} import edu.mit.csail.sdg.translator.{A4Solution, A4Tuple} import spray.json.{JsArray, JsObject, JsString, JsValue} -import scala.collection.JavaConversions._ import scala.collection.mutable.ListBuffer import scala.collection.JavaConverters._ diff --git a/src/main/scala/viper/server/frontends/lsp/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala index 59af933..af255e5 100644 --- a/src/main/scala/viper/server/frontends/lsp/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -17,6 +17,7 @@ import org.eclipse.lsp4j.{Diagnostic, DiagnosticSeverity, Location, Position, Pu import viper.server.frontends.lsp import viper.server.frontends.lsp.VerificationState._ import viper.server.frontends.lsp.VerificationSuccess._ +import viper.server.vsi.JobID import viper.silver.ast.{Domain, Field, Function, Method, Predicate, SourcePosition} import viper.silver.reporter._ @@ -92,7 +93,7 @@ class FileManager(file_uri: String) { } Log.info("Aborting running verification.") is_aborting = true - Coordinator.verifier.stopVerification(jid).thenAccept(_ => { + Coordinator.verifier.stopVerification(JobID(jid)).thenAccept(_ => { is_verifying = false lastSuccess = Aborted }).exceptionally(e => { @@ -158,13 +159,15 @@ class FileManager(file_uri: String) { // val command = startStageProcess(stage, path).getOrElse(return false) val command = startStageProcess(path.toString).getOrElse(return false) - println(s"Successfully generated command: $command") - jid = Coordinator.verifier.verify(command) - println(s"Successfully started verifying with id: $jid") - Log.debug(s"ViperServer started new job with ID ${jid}") - Coordinator.verifier.startStreaming(jid, RelayActor.props(this)) - Log.debug(s"Requesting ViperServer to stream results of verification job #${jid}...") - true + Log.info(s"Successfully generated command: $command") + val handle = Coordinator.verifier.verify(command) + jid = handle.id + if (jid >= 0) { + Coordinator.verifier.startStreaming(handle, RelayActor.props(this)) + true + } else { + false + } } object RelayActor { diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 0e953ba..d14fcc2 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -14,16 +14,18 @@ import akka.actor.{PoisonPill, Props} import akka.pattern.ask import akka.util.Timeout import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} -import viper.server.core.{VerificationJobHandler, ViperCache, ViperCoreServer} -import viper.server.protocol.ViperServerProtocol.Stop +import viper.server.core.{ViperCache, ViperCoreServer} import viper.server.utility.AstGenerator +import viper.server.vsi.VerificationProtocol.Stop +import viper.server.vsi.{JobID, VerificationProtocol, VerificationServer} import viper.silver.ast.Program +import viper.silver.reporter.PongMessage import scala.compat.java8.FutureConverters._ import scala.concurrent.Future import scala.concurrent.duration._ -class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { +class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with VerificationServer { protected var timeout: Int = _ @@ -69,7 +71,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } } - def verify(command: String): Int = { + def verify(command: String): JobID = { Log.debug("Requesting ViperServer to start new job...") val arg_list = getArgListFromArgString(command) @@ -84,11 +86,11 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { } catch { case _: java.nio.file.NoSuchFileException => Log.debug("The file for which verification has been requested was not found.") - return -1 + return JobID(-1) } val ast = ast_option.getOrElse({ Log.debug("The file for which verification has been requested contained syntax errors.") - return -1 + return JobID(-1) }) // prepare backend config @@ -98,31 +100,29 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) { case "custom" :: args => CustomConfig(args) } - val jid: VerificationJobHandler = verify(file, backend, ast) - + val jid: JobID = verify(file, backend, ast) if (jid.id >= 0) { - logger.get.info(s"Verification process #${jid.id} has successfully started.") + Log.info(s"Verification process #${jid.id} has successfully started.") } else { - logger.get.error(s"Could not start verification process. " + - s"The maximum number of active verification jobs are currently running (${MAX_ACTIVE_JOBS}).") - Log.debug(s"the maximum number of active verification jobs are currently running (${MAX_ACTIVE_JOBS}).") + Log.debug(s"Could not start verification process. " + + s"the maximum number of active verification jobs are currently running (${jobs.MAX_ACTIVE_JOBS}).") } - jid.id + jid } - def startStreaming(jid: Int, relayActor_props: Props): Unit = { + def startStreaming(jid: JobID, relayActor_props: Props): Unit = { Log.debug("Sending verification request to ViperServer...") val relay_actor = system.actorOf(relayActor_props) streamMessages(jid, relay_actor) } - def stopVerification(jid: Int): CFuture[Boolean] = { - lookupJob(jid) match { + def stopVerification(jid: JobID): CFuture[Boolean] = { + jobs.lookupJob(jid) match { case Some(handle_future) => handle_future.flatMap(handle => { implicit val askTimeout: Timeout = Timeout(config.actorCommunicationTimeout() milliseconds) - val interrupt: Future[String] = (handle.controller_actor ? Stop(true)).mapTo[String] - handle.controller_actor ! PoisonPill // the actor played its part. + val interrupt: Future[String] = (handle.job_actor ? Stop()).mapTo[String] + handle.job_actor ! PoisonPill // the actor played its part. interrupt }).toJava.toCompletableFuture.thenApply(msg => { Log.info(msg) diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 28d6327..e576d1f 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -27,7 +27,11 @@ case class JobNotFoundException() extends VerificationServerException case class JobID(id: Int) case class JobHandle(job_actor: ActorRef, queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) + publisher: Publisher[Envelope]) { + + object controller_actor + +} /** This class manages the verification jobs the server receives. */ From 25410105bccca49e0286c4244aca4738f8982029 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 18:00:40 +0200 Subject: [PATCH 11/79] change test start --- src/test/scala/ViperServerTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/ViperServerTests.scala b/src/test/scala/ViperServerTests.scala index 362308b..486e757 100644 --- a/src/test/scala/ViperServerTests.scala +++ b/src/test/scala/ViperServerTests.scala @@ -24,7 +24,7 @@ class ViperServerSpec extends WordSpec with Matchers with ScalatestRouteTest { implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() implicit val requestTimeput: RouteTestTimeout = RouteTestTimeout(10.second dilated) - ViperServerRunner.main(Array()) + ViperServerRunner.startHttpServer(Array()) private val _routsUnderTest = ViperServerRunner.viperServerHTTP.routes() From eb21f62b230142127ebf2472f9a2a501e6963793 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 13 Oct 2020 20:51:32 +0200 Subject: [PATCH 12/79] decremented positions to conform to LSP --- .../scala/viper/server/frontends/lsp/Common.scala | 7 ++----- .../viper/server/frontends/lsp/FileManager.scala | 14 +++++++------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/scala/viper/server/frontends/lsp/Common.scala b/src/main/scala/viper/server/frontends/lsp/Common.scala index 9b73f27..437d7e1 100644 --- a/src/main/scala/viper/server/frontends/lsp/Common.scala +++ b/src/main/scala/viper/server/frontends/lsp/Common.scala @@ -12,7 +12,8 @@ import java.net.URI import java.nio.file.{Path, Paths} import java.util.concurrent.CompletableFuture -import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.{Position, Range} + object Common { //TODO Get this from client programatically @@ -26,10 +27,6 @@ object Common { Path.of(uri) } -// def pathToUri(path: String): String = { -// Paths.get(path).toUri.toString -// } - def filenameFromUri(uri: String): String = { Paths.get(uri).getFileName.toString } diff --git a/src/main/scala/viper/server/frontends/lsp/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala index af255e5..103d654 100644 --- a/src/main/scala/viper/server/frontends/lsp/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -182,8 +182,8 @@ class FileManager(file_uri: String) { members.foreach(m => { val member_start = m.pos.asInstanceOf[SourcePosition].start val member_end = m.pos.asInstanceOf[SourcePosition].end.getOrElse(member_start) - val range_start = new Position(member_start.line, member_start.column) - val range_end = new Position(member_end.line, member_end.column) + val range_start = new Position(member_start.line - 1, member_start.column - 1) + val range_end = new Position(member_end.line - 1, member_end.column - 1) val range = new Range(range_start, range_end) val location: Location = new Location(file_uri, range) @@ -202,11 +202,11 @@ class FileManager(file_uri: String) { definitions = ArrayBuffer() defs.foreach(d => { val start = d.scope match { - case Some(s) => new Position(s.start.line, s.start.column) + case Some(s) => new Position(s.start.line - 1, s.start.column - 1) case None => null } val end = d.scope match { - case Some(s) if s.end.isDefined => new Position(s.end.get.line, s.end.get.column) + case Some(s) if s.end.isDefined => new Position(s.end.get.line - 1, s.end.get.column - 1) case None => null } val range: Range = if(start != null && end != null) { @@ -215,7 +215,7 @@ class FileManager(file_uri: String) { null } val sourcePos = d.location.asInstanceOf[viper.silver.ast.SourcePosition] - val location: Position = new Position(sourcePos.start.line, sourcePos.start.column) + val location: Position = new Position(sourcePos.start.line - 1, sourcePos.start.column - 1) val definition: lsp.Definition = lsp.Definition(d.typ, d.name, location, range) definitions.append(definition) }) @@ -245,9 +245,9 @@ class FileManager(file_uri: String) { } val err_start = err.pos.asInstanceOf[SourcePosition].start val err_end = err.pos.asInstanceOf[SourcePosition].end - val start_pos = new Position(err_start.line, err_start.column) + val start_pos = new Position(err_start.line - 1, err_start.column - 1) val end_pos = if(err_end.isDefined) { - new Position(err_end.get.line, err_end.get.column) + new Position(err_end.get.line - 1, err_end.get.column - 1) } else { null } From b102be3c93b63608af36f3b9589b7b798508e1f1 Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 14 Oct 2020 00:32:55 +0200 Subject: [PATCH 13/79] implemented Carbon/Silicon swap --- .../server/frontends/lsp/Coordinator.scala | 24 ++--------- .../server/frontends/lsp/DataProtocol.scala | 7 +++- .../frontends/lsp/IdeLanguageClient.scala | 2 +- .../viper/server/frontends/lsp/Receiver.scala | 32 ++++++++++---- .../frontends/lsp/ViperServerService.scala | 42 ++++++++++++------- 5 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala index 4fce3d8..143ea97 100644 --- a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala +++ b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala @@ -20,32 +20,18 @@ object Coordinator { var url: String = _ var client: IdeLanguageClient = _ -// var tempDirectory: String = pathHelper.join(os.tmpdir(), ".vscode") = _ -// var backendOutputDirectory: String = os.tmpdir() = _ -// var executedStages: ArrayBuffer[Stage] = _ - var documents: TextDocumentItem = new TextDocumentItem() var files = mutable.Map.empty[String, FileManager] - - var startingOrRestarting: Boolean = false - var backend: BackendProperties = _ - var verifier: ViperServerService = _ + var backend: BackendProperties = null + var verifier: ViperServerService = null def getAddress: String = url + ":" + port -// def stage: Option[Stage] = { -// if (executedStages != null && this.executedStages.nonEmpty) { -// Some(executedStages(executedStages.length - 1)) -// } else { -// None -// } -// } - def canVerificationBeStarted(uri: String, manuallyTriggered: Boolean): Boolean = { //check if there is already a verification task for that file if(files.get(uri).isEmpty){ Log.debug("No verification task found for file: " + uri) false - } else if (!verifier.isReady) { + } else if (!verifier.is_ready) { if (manuallyTriggered) { Log.hint("The verification backend is not ready yet") } @@ -79,8 +65,4 @@ object Coordinator { // Log.log("Update the decoration options (" + decorations.decorationOptions.length + ")", LogLevel.Debug) // client.stepsAsDecorationOptions(decorations) // } -// -// def sendStartBackendMessage(backend: String, forceRestart: Boolean, isViperServer: Boolean) { -// client.sendNotification(Commands.StartBackend, {backend: backend, forceRestart: forceRestart, isViperServer: isViperServer }) -// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala index b6e2b37..211ee1c 100644 --- a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala @@ -207,7 +207,12 @@ case class BackendOutput( // members: Array[Member] = null, //for Definitions definitions: Array[Definition] = null) -case class BackendReadyParams ( +case class BackendReadyParams( name: String, //name of the backend ready to use restarted: Boolean, //should the open file be reverified isViperServer: Boolean) + +case class BackendStartedParams( + name: String, + forceRestart: Boolean = false, + isViperServer: Boolean = true) \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala index a89d7ac..b60953b 100644 --- a/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala @@ -32,7 +32,7 @@ trait IdeLanguageClient extends LanguageClient { def notifyBackendReady(param: BackendReadyParams): Unit @JsonNotification(C2S_Commands.StartBackend) - def notifyBackendStarted(name: String, forceRestart: Boolean, isViperServer: Boolean): Unit + def notifyBackendStarted(params: BackendStartedParams): Unit @JsonNotification(S2C_Commands.BackendChange) def notifyBackendChanged(name: String): Unit diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index 1b480da..a802dcf 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -68,12 +68,17 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonNotification("workspace/didChangeConfiguration") def onDidChangeConfig(params: DidChangeConfigurationParams): Unit = { println("On config change") + Log.lowLevel("check if restart needed") try { - val b = BackendProperties( - "new Backend", "Silicon", null, null, - 5000, null, 5000, null) - Coordinator.verifier = new ViperServerService(Array()) - Coordinator.verifier.setReady(b) + if (Coordinator.verifier == null) { + Log.info("Change Backend: from 'No Backend' to Silicon") + Coordinator.backend = BackendProperties( + "ViperServer Silicon", "Silicon", null, null, + 5000, null, 5000, null) + Coordinator.client.notifyBackendStarted(BackendStartedParams("Silicon")) + } else { + Log.log("No need to restart backend. It is still the same", LogLevel.Debug) + } } catch { case e: Throwable => Log.debug("Error handling swap backend request: " + e) } @@ -207,11 +212,8 @@ class CustomReceiver extends StandardReceiver { def onStartBackend(backendName: String): Unit = { println("Starting ViperServeService") try { - val b = BackendProperties( - "new Backend", backendName, null, null, - 5000, null, 5000, null) Coordinator.verifier = new ViperServerService(Array()) - Coordinator.verifier.setReady(b) + Coordinator.verifier.setReady(Coordinator.backend) } catch { case e: Throwable => Log.debug("Error handling swap backend request: " + e) } @@ -229,6 +231,18 @@ class CustomReceiver extends StandardReceiver { } } + @JsonRequest(C2S_Commands.RequestBackendNames) + def onGetNames(backendName: String): CFuture[Array[String]] = { + CFuture.completedFuture(Array("Silicon", "Carbon")) + } + + + @JsonNotification(C2S_Commands.StopBackend) + def onBackendStop(backendName: String): Unit= { + Coordinator.verifier.setStopping() + Coordinator.verifier.setStopped() + } + @JsonNotification(C2S_Commands.Verify) def onVerify(data: VerifyRequest): Unit = { //it does not make sense to reverify if no changes were made and the verification is already running diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index d14fcc2..80d18b0 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -15,6 +15,7 @@ import akka.pattern.ask import akka.util.Timeout import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} import viper.server.core.{ViperCache, ViperCoreServer} +import viper.server.frontends.lsp.VerificationState.Stopped import viper.server.utility.AstGenerator import viper.server.vsi.VerificationProtocol.Stop import viper.server.vsi.{JobID, VerificationProtocol, VerificationServer} @@ -29,38 +30,47 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with protected var timeout: Int = _ - def isReady: Boolean = isRunning + def isRunningServer: Boolean = isRunning + + var is_ready = false def setReady(backend: BackendProperties): Unit = { Coordinator.backend = backend start() - Coordinator.startingOrRestarting = true + is_ready = true val param = BackendReadyParams("Silicon", false, true) Coordinator.client.notifyBackendReady(param) Log.info("The backend is ready for verification") } def swapBackend(newBackend: BackendProperties): Unit = { + is_ready = true Coordinator.backend = newBackend - Coordinator.startingOrRestarting = false val param = BackendReadyParams("Silicon", false, true) Coordinator.client.notifyBackendReady(param) Log.info("The backend has been swapped and is now ready for verification") } -// def setStopping(): Unit = { -// Log.debug("Set Stopping... ") -// isRunning = false -// Coordinator.startingOrRestarting = false -// Coordinator.sendStateChangeNotification(StateChangeParams(Stopping.id), None) -// } -// -// def setStopped(): Unit = { -// Log.debug("Set Stopped. ") -// isRunning = false -// Coordinator.startingOrRestarting = false -// Coordinator.sendStateChangeNotification(StateChangeParams(Stopped.id), None) -// } + def setStopping(): Unit = { + Log.debug("Set Stopping... ") + if(isRunning){ + isRunning = false + val params = StateChangeParams(Stopped.id) + Coordinator.sendStateChangeNotification(params, None) + } else { + Log.debug("Server stopped") + } + } + def setStopped(): Unit = { + Log.debug("Set Stopped. ") + if(isRunning){ + isRunning = false + val params = StateChangeParams(Stopped.id) + Coordinator.sendStateChangeNotification(params, None) + } else { + Log.debug("Server stopped") + } + } private def getArgListFromArgString(arg_str: String): List[String] = { val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r From 90cb817871820cdb2cf12422c22c568d6683e41d Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 14 Oct 2020 12:45:23 +0200 Subject: [PATCH 14/79] implemented backend swap --- .../frontends/lsp/CommandProtocol.scala | 2 + .../server/frontends/lsp/Coordinator.scala | 22 +- .../server/frontends/lsp/DataProtocol.scala | 219 +++++++++--------- .../server/frontends/lsp/FileManager.scala | 20 +- .../frontends/lsp/IdeLanguageClient.scala | 2 +- .../viper/server/frontends/lsp/Receiver.scala | 57 ++--- .../frontends/lsp/ViperServerService.scala | 8 - 7 files changed, 161 insertions(+), 169 deletions(-) diff --git a/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala index b8e2fdb..a1a5a89 100644 --- a/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala @@ -24,6 +24,7 @@ object C2S_Commands { final val ViperUpdateComplete = "ViperUpdateComplete" final val FlushCache = "FlushCache" final val GetIdentifier = "GetIdentifier" + } object S2C_Commands { @@ -45,4 +46,5 @@ object S2C_Commands { final val StepsAsDecorationOptions = "StepsAsDecorationOptions" //StepsAsDecorationOptionsResult final val HeapGraph = "HeapGraph" //HeapGraph final val UnhandledViperServerMessageType = "UnhandledViperServerMessageType" + final val BackendStarted = "BackendStarted" } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala index 143ea97..45ddcfa 100644 --- a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala +++ b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala @@ -20,12 +20,16 @@ object Coordinator { var url: String = _ var client: IdeLanguageClient = _ - var files = mutable.Map.empty[String, FileManager] - var backend: BackendProperties = null - var verifier: ViperServerService = null + var files = mutable.Map.empty[String, FileManager] //Each file is managed individually. + var verifier: ViperServerService = null // verification enginge. Set once on start. + var backend: BackendProperties = null // Backend the engine uses. Can be swapped throughout def getAddress: String = url + ":" + port + /** Checks if verification can be started for a given file. + * + * Informs client differently depending on whether or not verification attempt was triggered manually + * */ def canVerificationBeStarted(uri: String, manuallyTriggered: Boolean): Boolean = { //check if there is already a verification task for that file if(files.get(uri).isEmpty){ @@ -42,6 +46,11 @@ object Coordinator { } } + /** Stops all running verifications. + * + * Returns a CF that is successfully completed if all running verifications were stopped + * successfully. Otherwise, a failed CF is returned + * */ def stopAllRunningVerifications(): CompletableFuture[Void] = { if (files.nonEmpty) { val promises = files.values.map(task => task.abortVerification()).toSeq @@ -51,9 +60,12 @@ object Coordinator { } } - //Communication requests and notifications sent to language client + /** Notifies the client about a state change + * + * If state change is related to a particular file, its manager's state is also updated. + * */ def sendStateChangeNotification(params: StateChangeParams, task: Option[FileManager]): Unit = { - if (task.isDefined) task.get.state = VerificationState.apply(params.newState.toInt) + if (task.isDefined) task.get.state = VerificationState.apply(params.newState) try { client.notifyStateChanged(params) } catch { diff --git a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala index 211ee1c..b725122 100644 --- a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala @@ -50,6 +50,20 @@ object LogLevel extends Enumeration { val LowLevelDebug = Value // all output of used tools is written to logFile, } // some of it also to the console +object BackendOutputType { + val Start = "Start" + val End = "End" + val VerificationStart = "VerificationStart" + val MethodVerified = "MethodVerified" + val FunctionVerified = "FunctionVerified" + val PredicateVerified = "PredicateVerified" + val Error = "Error" + val Outline = "Outline" + val Definitions = "Definitions" + val Success = "Success" + val Stopped = "Stopped" +} + case class ProgressReport ( domain: String, current: Double, @@ -57,34 +71,15 @@ case class ProgressReport ( progress: Double, postfix: Double) -case class Hint(msg: String, showButton1: Boolean, showButton2: Boolean) - -//Might -case class StateChangeParams( - newState: Int, - progress: Double = -1, - success: Int = NA.id, - verificationCompleted: Double = -1, - manuallyTriggered: Double = -1, - filename: String = null, - backendName: String = null, - time: Double = -1, - nofErrors: Double = -1, - verificationNeeded: Double = -1, - uri: String = null, - stage: String = null, - error: String = null, - diagnostics: Array[Diagnostic] = null) - case class BackendProperties( name: String, backend_type: String, - paths: Array[String], - engine: String, - timeout: Int, - stages: Array[Stage], - stoppingTimeout: Int, - version: String) extends VersionedSettings + paths: Array[String] = null, + engine: String = "Viper", + timeout: Int = 5000, + stages: Array[Stage] = null, + stoppingTimeout: Int = 5000, + version: String = null) case class VerifyRequest ( uri: String, // file to verify @@ -93,40 +88,6 @@ case class VerifyRequest ( case class SettingsError (errorType: SettingsErrorType, msg: String) -trait VersionedSettings { - val version: String -} - -//TODO Care that serverJars can take other types. These must be translated to a String. -case class ViperServerSettings( - serverJars: Array[String], // Locator to the ViperServer jars - customArguments: String, // custom commandLine arguments - backendSpecificCache: Boolean, // it set to false, cached errors are reused across backends - disableCaching: Boolean, // disable the caching mechanism - timeout: Int, // After timeout ms the startup of the viperServer is expected to have failed and thus aborted - viperServerPolicy: String, // Specifies whether ViperServer should be started by the IDE or whether the IDE should attach to an existing instance of ViperServer. Possible values: "attach", "create". - viperServerAddress: String, // Specifies the address part of the URL that ViperServer is running on. - viperServerPort: Int, // Specifies the port part of the URL that ViperServer is running on. Only needed if viperServerPolicy is set to 'attach'. - version: String ) extends VersionedSettings - -case class Versions( - viperServerSettingsVersion: String, - backendSettingsVersion: String, - pathSettingsVersion: String, - userPreferencesVersion: String, - javaSettingsVersion: String, - advancedFeaturesVersion: String, - defaultSettings: AnyRef, - extensionVersion: String) - -case class ViperSettings( - viperServerSettings: ViperServerSettings, // All viperServer related settings - verificationBackends: Array[BackendProperties], // Description of backends - paths: PathSettings, // Used paths - preferences: UserPreferences, // General user preferences - javaSettings: JavaSettings, // Java settings - advancedFeatures: AdvancedFeatureSettings) // Settings for AdvancedFeatures - case class Stage( name: String, //The per backend unique name of this stage isVerification: Boolean, //Enable if this stage is describing a verification @@ -137,13 +98,6 @@ case class Stage( onVerificationError: String, //The name of the stage to start in case of a verification error onSuccess: String) //The name of the stage to start in case of a success -case class PathSettings( - viperToolsPath: Either[String, PlatformDependentPath], // Path to the folder containing all the ViperTools - z3Executable: Either[String, PlatformDependentPath], // The path to the z3 executable - boogieExecutable: Either[String, PlatformDependentPath], // The path to the boogie executable - version: String) extends VersionedSettings - - case class PlatformDependentPath ( windows: Option[String], mac: Option[String], @@ -154,46 +108,9 @@ case class PlatformDependentURL ( mac: Option[String], linux: Option[String]) -case class UserPreferences ( - autoSave: Boolean, //Enable automatically saving modified viper files - logLevel: Int, //Verbosity of the output, all output is written to the logFile, regardless of the logLevel - autoVerifyAfterBackendChange: Boolean, //Reverify the open viper file upon backend change. - showProgress: Boolean, //Display the verification progress in the status bar. Only useful if the backend supports progress reporting. - viperToolsProvider: Either[String, PlatformDependentURL], //The URL for downloading the ViperTools from - version: String) extends VersionedSettings - -//The arguments used for all java invocations -case class JavaSettings(customArguments: String, version: String) extends VersionedSettings - -case class AdvancedFeatureSettings( - enabled: Boolean, //Enable heap visualization, stepwise debugging and execution path visualization - showSymbolicState: Boolean, //Show the symbolic values in the heap visualization. If disabled, the symbolic values are only shown in the error states. - darkGraphs: Boolean, //To get the best visual heap representation, this setting should match with the active theme. - simpleMode: Boolean, //Useful for verifying programs. Disable when developing the backend - showOldState: Boolean, //Visualize also the oldHeap in the heap preview - showPartialExecutionTree: Boolean, //Show the part of the execution tree around the current state in the state visualization - verificationBufferSize: Int, //Maximal buffer size for verification in KB - compareStates: Boolean, - version: String) extends VersionedSettings - - // scope == null means global scope case class Definition(definition_type: String, name: String, code_location: Position, scope: Range) -object BackendOutputType { - val Start = "Start" - val End = "End" - val VerificationStart = "VerificationStart" - val MethodVerified = "MethodVerified" - val FunctionVerified = "FunctionVerified" - val PredicateVerified = "PredicateVerified" - val Error = "Error" - val Outline = "Outline" - val Definitions = "Definitions" - val Success = "Success" - val Stopped = "Stopped" -} - case class BackendOutput( typ: String, name: String = null, @@ -207,12 +124,100 @@ case class BackendOutput( // members: Array[Member] = null, //for Definitions definitions: Array[Definition] = null) +//////////////////////////////////////////////////////////////////////////////////////////////////// +////// SETTINGS /////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +case class Hint(msg: String, showButton1: Boolean, showButton2: Boolean) + case class BackendReadyParams( - name: String, //name of the backend ready to use - restarted: Boolean, //should the open file be reverified - isViperServer: Boolean) + name: String, //name of the backend ready to use + restarted: Boolean, + isViperServer: Boolean) case class BackendStartedParams( name: String, forceRestart: Boolean = false, - isViperServer: Boolean = true) \ No newline at end of file + isViperServer: Boolean = true) + +case class StateChangeParams( + newState: Int, + progress: Double = -1, + success: Int = NA.id, + verificationCompleted: Double = -1, + manuallyTriggered: Double = -1, + filename: String = null, + backendName: String = null, + time: Double = -1, + nofErrors: Double = -1, + verificationNeeded: Double = -1, + uri: String = null, + stage: String = null, + error: String = null, + diagnostics: Array[Diagnostic] = null) + +//////////////////////////////////////////////////////////////////////////////////////////////////// +////// SETTINGS /////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//TODO Care that serverJars can take other types. These must be translated to a String. +case class ViperServerSettings( + serverJars: Array[String], // Locator to the ViperServer jars + customArguments: String, // custom commandLine arguments + backendSpecificCache: Boolean, // it set to false, cached errors are reused across backends + disableCaching: Boolean, // disable the caching mechanism + timeout: Int, // After timeout ms the startup of the viperServer is expected to have failed and thus aborted + viperServerPolicy: String, // Specifies whether ViperServer should be started by the IDE or whether the IDE should attach to an existing instance of ViperServer. Possible values: "attach", "create". + viperServerAddress: String, // Specifies the address part of the URL that ViperServer is running on. + viperServerPort: Int, // Specifies the port part of the URL that ViperServer is running on. Only needed if viperServerPolicy is set to 'attach'. + version: String ) extends VersionedSettings + +case class Versions( + viperServerSettingsVersion: String, + backendSettingsVersion: String, + pathSettingsVersion: String, + userPreferencesVersion: String, + javaSettingsVersion: String, + advancedFeaturesVersion: String, + defaultSettings: AnyRef, + extensionVersion: String) + +case class ViperSettings( + viperServerSettings: ViperServerSettings, // All viperServer related settings + verificationBackends: Array[BackendProperties], // Description of backends + paths: PathSettings, // Used paths + preferences: UserPreferences, // General user preferences + javaSettings: JavaSettings, // Java settings + advancedFeatures: AdvancedFeatureSettings) // Settings for AdvancedFeatures + +trait VersionedSettings { + val version: String +} + +case class PathSettings( + viperToolsPath: Either[String, PlatformDependentPath], // Path to the folder containing all the ViperTools + z3Executable: Either[String, PlatformDependentPath], // The path to the z3 executable + boogieExecutable: Either[String, PlatformDependentPath], // The path to the boogie executable + version: String) extends VersionedSettings + +case class UserPreferences ( + autoSave: Boolean, //Enable automatically saving modified viper files + logLevel: Int, //Verbosity of the output, all output is written to the logFile, regardless of the logLevel + autoVerifyAfterBackendChange: Boolean, //Reverify the open viper file upon backend change. + showProgress: Boolean, //Display the verification progress in the status bar. Only useful if the backend supports progress reporting. + viperToolsProvider: Either[String, PlatformDependentURL], //The URL for downloading the ViperTools from + version: String) extends VersionedSettings + +//The arguments used for all java invocations +case class JavaSettings(customArguments: String, version: String) extends VersionedSettings + +case class AdvancedFeatureSettings( + enabled: Boolean, //Enable heap visualization, stepwise debugging and execution path visualization + showSymbolicState: Boolean, //Show the symbolic values in the heap visualization. If disabled, the symbolic values are only shown in the error states. + darkGraphs: Boolean, //To get the best visual heap representation, this setting should match with the active theme. + simpleMode: Boolean, //Useful for verifying programs. Disable when developing the backend + showOldState: Boolean, //Visualize also the oldHeap in the heap preview + showPartialExecutionTree: Boolean, //Show the part of the execution tree around the current state in the state visualization + verificationBufferSize: Int, //Maximal buffer size for verification in KB + compareStates: Boolean, + version: String) extends VersionedSettings \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala index 103d654..76f87e2 100644 --- a/src/main/scala/viper/server/frontends/lsp/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -106,8 +106,7 @@ class FileManager(file_uri: String) { // def startStageProcess(stage: Stage, fileToVerify: String): Option[String] = { try { Log.lowLevel("Start Stage Process") -// Some(getStageCommand(fileToVerify, stage)) - Some("silicon " + fileToVerify) + Some(getStageCommand(fileToVerify, null)) } catch { case e: Throwable => Log.debug("Error starting stage process: " + e) @@ -116,18 +115,17 @@ class FileManager(file_uri: String) { } def getStageCommand(fileToVerify: String, stage: Stage): String = { - val args: String = getViperBackendClassName(stage) + " " + stage.customArguments + val args: String = getViperBackendClassName(stage) + s" $fileToVerify" // val command = Settings.expandCustomArguments(args, stage, fileToVerify, Coordinator.backend) - val command = "" - Log.debug(command) - command + Log.debug(args) + args } def getViperBackendClassName(stage: Stage): String = { Coordinator.backend.backend_type match { - case "silicon" => "silicon" - case "carbon" => "carbon" - case "other" => stage.mainMethod + case "Silicon" => "silicon" + case "Carbon" => "carbon" +// case "other" => stage.mainMethod case _ => throw new Error(s"Invalid verification backend value. " + s"Possible values are [silicon | carbon | other] " + s"but found ${Coordinator.backend}") @@ -149,8 +147,8 @@ class FileManager(file_uri: String) { // val stage = Coordinator.backend.stages.head // Coordinator.executedStages.append(stage) - Log.info("verify " + filename) - Log.info(Coordinator.backend.name + " verification started") + Log.info(s"verify $filename") + Log.info(s"${Coordinator.backend.name} verification started") val params = StateChangeParams(VerificationRunning.id, filename = filename) println("state change params created") diff --git a/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala index b60953b..6edc3c0 100644 --- a/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala @@ -31,7 +31,7 @@ trait IdeLanguageClient extends LanguageClient { @JsonNotification(S2C_Commands.BackendReady) def notifyBackendReady(param: BackendReadyParams): Unit - @JsonNotification(C2S_Commands.StartBackend) + @JsonNotification(S2C_Commands.BackendStarted) def notifyBackendStarted(params: BackendStartedParams): Unit @JsonNotification(S2C_Commands.BackendChange) diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index a802dcf..2292cb8 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -68,14 +68,13 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonNotification("workspace/didChangeConfiguration") def onDidChangeConfig(params: DidChangeConfigurationParams): Unit = { println("On config change") - Log.lowLevel("check if restart needed") try { + Log.lowLevel("check if restart needed") if (Coordinator.verifier == null) { Log.info("Change Backend: from 'No Backend' to Silicon") - Coordinator.backend = BackendProperties( - "ViperServer Silicon", "Silicon", null, null, - 5000, null, 5000, null) - Coordinator.client.notifyBackendStarted(BackendStartedParams("Silicon")) + val backend = "Silicon" + Coordinator.backend = BackendProperties(name = s"Viper$backend", backend_type = backend) + Coordinator.client.notifyBackendStarted(BackendStartedParams(backend)) } else { Log.log("No need to restart backend. It is still the same", LogLevel.Debug) } @@ -157,31 +156,6 @@ abstract class StandardReceiver extends LanguageClientAware { Coordinator.verifier.stop() if(received_shutdown) sys.exit(0) else sys.exit(1) } - -// @JsonRequest("textDocument/completion") -// def completion(params: CompletionParams): CFuture[CompletionList] = { -// val tsItem = new CompletionItem("Do the completion for Viper!!") -// tsItem.setKind(CompletionItemKind.Text) -// tsItem.setData(1) -// val completions = new CompletionList(List(tsItem).asJava) -// CFuture.completedFuture(completions) -// } -// -// @JsonRequest("completionItem/resolve") -// def completionItemResolve(item: CompletionItem): CFuture[CompletionItem] = { -// val data: Object = item.getData -// data match { -// case n: JsonPrimitive if n.getAsInt == 1 => -// item.setDetail("TypeScript details") -// item.setDocumentation("TypeScript documentation") -// case n: JsonPrimitive if n.getAsInt == 2 => -// item.setDetail("JavaScript details") -// item.setDocumentation("JavaScript documentation") -// case _ => -// item.setDetail(s"${data.toString} is instance of ${data.getClass}") -// } -// CFuture.completedFuture(item) -// } } class CustomReceiver extends StandardReceiver { @@ -209,9 +183,14 @@ class CustomReceiver extends StandardReceiver { } @JsonNotification(C2S_Commands.StartBackend) - def onStartBackend(backendName: String): Unit = { + def onStartBackend(backend: String): Unit = { println("Starting ViperServeService") try { + if(backend == "Silicon" || backend == "Carbon") { + Coordinator.backend = BackendProperties(name = s"Viper$backend", backend_type = backend) + } else { + throw new Throwable("Unexpected Backend") + } Coordinator.verifier = new ViperServerService(Array()) Coordinator.verifier.setReady(Coordinator.backend) } catch { @@ -220,12 +199,17 @@ class CustomReceiver extends StandardReceiver { } @JsonNotification(C2S_Commands.SwapBackend) - def onSwapBackend(backendName: String): Unit = { + def onSwapBackend(backend: String): Unit = { try { - val b = BackendProperties( - "new Backend", backendName, null, null, - 5000, null, 5000, null) - Coordinator.verifier.swapBackend(b) + if(backend == "Silicon" || backend == "Carbon") { + Coordinator.backend = BackendProperties(name = s"Viper$backend", backend_type = backend) + } else { + throw new Throwable("Unexpected Backend") + } + Coordinator.verifier.is_ready = true + val param = BackendReadyParams(backend, false, true) + Coordinator.client.notifyBackendReady(param) + Log.info("The backend has been swapped and is now ready for verification") } catch { case e: Throwable => Log.debug("Error handling swap backend request: " + e) } @@ -236,7 +220,6 @@ class CustomReceiver extends StandardReceiver { CFuture.completedFuture(Array("Silicon", "Carbon")) } - @JsonNotification(C2S_Commands.StopBackend) def onBackendStop(backendName: String): Unit= { Coordinator.verifier.setStopping() diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 80d18b0..5b2cb5a 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -43,14 +43,6 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with Log.info("The backend is ready for verification") } - def swapBackend(newBackend: BackendProperties): Unit = { - is_ready = true - Coordinator.backend = newBackend - val param = BackendReadyParams("Silicon", false, true) - Coordinator.client.notifyBackendReady(param) - Log.info("The backend has been swapped and is now ready for verification") - } - def setStopping(): Unit = { Log.debug("Set Stopping... ") if(isRunning){ From 820d2dc471914b331505edd483adea1b6bbe9463 Mon Sep 17 00:00:00 2001 From: Valentin Date: Mon, 19 Oct 2020 23:27:46 +0200 Subject: [PATCH 15/79] Split Server runner into two, one for each frontend --- .../scala/viper/server/HttpServerRunner.scala | 24 + src/main/scala/viper/server/ViperServer.scala | 13 - .../viper/server/frontends/lsp/Common.scala | 257 ----- .../server/frontends/lsp/DataProtocol.scala | 1 - .../viper/server/frontends/lsp/Receiver.scala | 13 +- .../viper/server/frontends/lsp/Settings.scala | 901 ------------------ .../frontends/lsp/ViperServerService.scala | 3 +- src/test/scala/ViperServerTests.scala | 4 +- 8 files changed, 29 insertions(+), 1187 deletions(-) create mode 100644 src/main/scala/viper/server/HttpServerRunner.scala delete mode 100644 src/main/scala/viper/server/frontends/lsp/Settings.scala diff --git a/src/main/scala/viper/server/HttpServerRunner.scala b/src/main/scala/viper/server/HttpServerRunner.scala new file mode 100644 index 0000000..bb5624e --- /dev/null +++ b/src/main/scala/viper/server/HttpServerRunner.scala @@ -0,0 +1,24 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + +import viper.server.frontends.http.ViperHttpServer + +object HttpServerRunner { + var viperServerHTTP: ViperHttpServer = _ + + /** Start VCS in HTTP mode. + * */ + def startHttpServer(args: Array[String]): Unit = { + viperServerHTTP = new ViperHttpServer(args) + viperServerHTTP.start() + } + + def main(args: Array[String]): Unit = { + startHttpServer(args) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index 53f63da..16bb269 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -10,22 +10,10 @@ import java.io.IOException import java.net.Socket import org.eclipse.lsp4j.jsonrpc.Launcher -import org.eclipse.lsp4j.services.LanguageClient -import viper.server.frontends.http.ViperHttpServer import viper.server.frontends.lsp.{Coordinator, CustomReceiver, IdeLanguageClient} object ViperServerRunner { - var viperServerHTTP: ViperHttpServer = _ - - /** Start VCS in HTTP mode. - * */ - def startHttpServer(args: Array[String]): Unit = { - - viperServerHTTP = new ViperHttpServer(args) - viperServerHTTP.start() - } - def main(args: Array[String]): Unit = { try { val port = Integer.parseInt(args.head) @@ -38,7 +26,6 @@ object ViperServerRunner { case _: NumberFormatException => { println("Invalid port number") sys.exit(1) - return } } } diff --git a/src/main/scala/viper/server/frontends/lsp/Common.scala b/src/main/scala/viper/server/frontends/lsp/Common.scala index 437d7e1..3b59e42 100644 --- a/src/main/scala/viper/server/frontends/lsp/Common.scala +++ b/src/main/scala/viper/server/frontends/lsp/Common.scala @@ -60,32 +60,6 @@ object Common { } } -// def prettyRange(range: Range): String = { -// s"${prettyPos(range.start)}-${prettyPos(range.end)}" -// } - -// def prettyPos(pos: Position): String = { -// s"${pos.line + 1}:${pos.character + 1}" -// } - -// def getPositionOfState(index): Position = { -// if (index >= 0 && index < this.steps.length) { -// if (this.steps[index].position) { -// return this.steps[index].position -// } else { -// return { -// line: 0 -// , character: 0 -// } -// } -// } else { -// return { -// line: -1 -// , character: -1 -// } -// } -// } - def comparePosition(a: Position, b: Position): Int = { if (a == null && b == null) return 0 if (a != null) return -1 @@ -98,235 +72,4 @@ object Common { 1 } } - -// private def comparePositionAndIndex(a: Statement, b: Statement): Int = { -// if (!a && !b) return 0 -// if (!a) return -1 -// if (!b) return 1 -// if (a.position.line < b.position.line || (a.position.line === b.position.line && a.position.character < b.position.character)) { -// return -1 -// } else if (a.position.line === b.position.line && a.position.character === b.position.character) { -// return (a.index < b.index) ? -1: 1 -// } else { -// return 1 -// } -// } - -// private def compareByIndex(a: Statement, b: Statement): Int = { -// if (!a && !b) return 0 -// if (!a) return -1 -// if (!b) return 1 -// if (a.index < b.index) { -// return -1 -// } else if (a.index === b.index) { -// return 0 -// } else { -// return 1 -// } -// } - -// private def prettySteps(steps: Array[Statement]): String = { -// try { -// val res: String = "" -// val methodIndex = - 1 -// val currentMethodOffset = - 1 -// val maxLine = 0 -// val indent = "" -// val allBordersPrinted = false -// -// // val currentMethod -// -// val numberOfClientSteps = 0 -// steps.foreach((element, i) => { -// val clientNumber = element.decorationOptions ? "" + element.decorationOptions.numberToDisplay: "" -// -// if (element.canBeShownAsDecoration) { -// numberOfClientSteps ++ -// } -// -// val parent = element.getClientParent () -// if (parent && element.decorationOptions) { -// clientNumber += " " + parent.decorationOptions.numberToDisplay -// } -// -// val serverNumber = "" + i -// val spacesToPut = 8 - clientNumber.length - serverNumber.length -// spacesToPut = spacesToPut < 0 ? 0: spacesToPut -// res += `\n\t${clientNumber} ${"\t".repeat(spacesToPut)}(${serverNumber})|${"\t".repeat(element.depthLevel())} ${element.firstLine()}` -// }) -// -// res += '\ nNumberOfClientSteps: ' + numberOfClientSteps -// //Log.log("Steps:\n" + res, LogLevel.LowLevelDebug) -// return res -// } catch (e) { -// Log.debug ("Runtime Error in Pretty Steps: " + e) -// } -// } - - //Helper methods for child processes -// def executer(command: String, dataHandler?: (String) => void, errorHandler?: (String) => void, exitHandler?: () => void): child_process.ChildProcess = { -// try { -// Log.logWithOrigin("executer", command, LogLevel.Debug) -// val child: child_process.ChildProcess = child_process.exec(command, function (error, stdout, stderr) { -// Log.logWithOrigin('executer:stdout', stdout, LogLevel.LowLevelDebug) -// if (dataHandler) { -// dataHandler(stdout) -// } -// Log.logWithOrigin('executer:stderr', stderr, LogLevel.LowLevelDebug) -// if (errorHandler) { -// errorHandler(stderr) -// } -// if (error !== null) { -// Log.logWithOrigin('executer', ''+error, LogLevel.LowLevelDebug) -// } -// if (exitHandler) { -// Log.logWithOrigin('executer', 'done', LogLevel.LowLevelDebug) -// exitHandler() -// } -// }) -// return child -// } catch (e) { -// Log.error("Error executing " + command + ": " + e) -// } -// } - -// def sudoExecuter(command: String, name: String, callback) = { -// Log.log("sudo-executer: " + command, LogLevel.Debug) -// val options = { name: name } -// val child = sudo.exec(command, options, function (error, stdout, stderr) { -// Log.logWithOrigin('stdout', stdout, LogLevel.LowLevelDebug) -// Log.logWithOrigin('stderr', stderr, LogLevel.LowLevelDebug) -// if (error) { -// Log.error('sudo-executer error: ' + error) -// } -// callback() -// }) -// } - -// def spawner(command: String, args: String[]): child_process.ChildProcess = { -// Log.log("spawner: " + command + " " + args.join(" "), LogLevel.Debug) -// try { -// val child = child_process.spawn(command, args, { detached: true }) -// child.on('stdout', data => { -// Log.logWithOrigin('spawner stdout', data, LogLevel.LowLevelDebug) -// }) -// child.on('stderr', data => { -// Log.logWithOrigin('spawner stderr', data, LogLevel.LowLevelDebug) -// }) -// child.on('exit', data => { -// Log.log('spawner done: ' + data, LogLevel.LowLevelDebug) -// }) -// return child -// } catch (e) { -// Log.error("Error spawning command: " + e) -// } -// } - -// def backendRestartNeeded(settings: ViperSettings, oldBackendName: String, newBackendName: String) = { -// if (!settings) -// return true -// -// val oldBackend = settings.verificationBackends.find(value => value.name == oldBackendName) -// val newBackend = settings.verificationBackends.find(value => value.name == newBackendName) -// -// if (oldBackend && newBackend && oldBackend.engine.toLowerCase() == 'viperserver' && newBackend.engine.toLowerCase() == 'viperserver') -// return false -// -// return true -// } - -// def makeSureFileExistsAndCheckForWritePermission(filePath: String, firstTry = true): Future[any] = { -// return new Future((resolve, reject) => { -// try { -// val folder = pathHelper.dirname(filePath) -// mkdirp(folder, (err) => { -// if (err && err.code != 'EEXIST') { -// resolve(err.code + ": Error creating " + folder + " " + err.message) -// } else { -// fs.open(filePath, 'a', (err, file) => { -// if (err) { -// resolve(err.code + ": Error opening " + filePath + " " + err.message) -// } else { -// fs.close(file, err => { -// if (err) { -// resolve(err.code + ": Error closing " + filePath + " " + err.message) -// } else { -// fs.access(filePath, 2, (e) => { //fs.constants.W_OK is 2 -// if (e) { -// resolve(e.code + ": Error accessing " + filePath + " " + e.message) -// } else { -// resolve(null) -// } -// }) -// } -// }) -// } -// }) -// } -// }) -// } catch (e) { -// resolve(e) -// } -// }) -// } - -// def extract(filePath: String): Future[Boolean] = { -// return new Future((resolve, reject) => { -// try { -// //extract files -// Log.log("Extracting files...", LogLevel.Info) -// Log.startProgress() -// val unzipper = new DecompressZip(filePath) -// -// unzipper.on('progress', function (fileIndex, fileCount) { -// Log.progress("Extracting Viper Tools", fileIndex + 1, fileCount, LogLevel.Debug) -// }) -// -// unzipper.on('error', function (e) { -// if (e.code && e.code == 'ENOENT') { -// Log.debug("Error updating the Viper Tools, missing create file permission in the viper tools directory: " + e) -// } else if (e.code && e.code == 'EACCES') { -// Log.debug("Error extracting " + filePath + ": " + e + " | " + e.message) -// } else { -// Log.debug("Error extracting " + filePath + ": " + e) -// } -// resolve(false) -// }) -// -// unzipper.on('extract', function (log) { -// resolve(true) -// }) -// -// unzipper.extract({ -// path: pathHelper.dirname(filePath), -// filter: function (file) { -// return file.type !== "SymbolicLink" -// } -// }) -// } catch (e) { -// Log.debug("Error extracting viper tools: " + e) -// resolve(false) -// } -// }) -// } - -// def getParentDir(fileOrFolderPath: String): String = { -// if (!fileOrFolderPath) return null -// val obj = pathHelper.parse(fileOrFolderPath) -// if (obj.base) { -// return obj.dir -// } -// val folderPath = obj.dir -// val is_matching = folderPath.match(/(^.*)[\/\\].+$/) //the regex retrieves the parent directory -// if (is_matching) { -// if (is_matching[1] == fileOrFolderPath) { -// Log.debug("getParentDir has a fixpoint at " + fileOrFolderPath) -// return null -// } -// return match[1] -// } -// else { -// return null -// } -// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala index b725122..519076f 100644 --- a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala @@ -160,7 +160,6 @@ case class StateChangeParams( ////// SETTINGS /////// //////////////////////////////////////////////////////////////////////////////////////////////////// -//TODO Care that serverJars can take other types. These must be translated to a String. case class ViperServerSettings( serverJars: Array[String], // Locator to the ViperServer jars customArguments: String, // custom commandLine arguments diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index 2292cb8..9cbb925 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -24,15 +24,9 @@ abstract class StandardReceiver extends LanguageClientAware { var received_shutdown = false @JsonRequest("initialize") - def initialize(params: InitializeParams): CFuture[InitializeResult] = { + def onInitialize(params: InitializeParams): CFuture[InitializeResult] = { println("initialize") val capabilities = new ServerCapabilities() - // OG - // always send full text document for each notification: - // capabilities.setCompletionProvider(new CompletionOptions(true, null)) - - // val Coordinator.verifier = new ViperServerService(Array()) - // Coordinator.verifier.setReady(Option.empty) capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) capabilities.setDefinitionProvider(true) @@ -40,7 +34,7 @@ abstract class StandardReceiver extends LanguageClientAware { CFuture.completedFuture(new InitializeResult(capabilities)) } @JsonNotification("initialized") - def initialize(params: InitializedParams): Unit = { + def onInitialized(params: InitializedParams): Unit = { println("initialized") } @@ -228,13 +222,10 @@ class CustomReceiver extends StandardReceiver { @JsonNotification(C2S_Commands.Verify) def onVerify(data: VerifyRequest): Unit = { - //it does not make sense to reverify if no changes were made and the verification is already running if (Coordinator.canVerificationBeStarted(data.uri, data.manuallyTriggered)) { -// Settings.workspace = data.workspace //stop all other verifications because the backend crashes if multiple verifications are run in parallel Coordinator.stopAllRunningVerifications().thenAccept(_ => { println("Verifications stopped successfully") -// Coordinator.executedStages = ArrayBuffer() Log.log("start or restart verification", LogLevel.Info) val manager = Coordinator.files.getOrElse(data.uri, return) diff --git a/src/main/scala/viper/server/frontends/lsp/Settings.scala b/src/main/scala/viper/server/frontends/lsp/Settings.scala deleted file mode 100644 index bb7365d..0000000 --- a/src/main/scala/viper/server/frontends/lsp/Settings.scala +++ /dev/null @@ -1,901 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ - -package viper.server.frontends.lsp - -import java.util.concurrent.{CompletableFuture => CFuture} - -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} -// -//case class ResolvedPath (path: String, exists: Boolean, error: Option[String]) -// -object Settings { - -// def checkSettings(viperToolsUpdated: Boolean): CFuture[Unit] = ??? -// -// def valid(): Boolean = ??? - - // implicit val ex = ExecutionContext.global -// var settings: ViperSettings -// var isWin = System.getProperties.get("os.name") -// var isLinux = /^linux/.test(process.platform) -// var isMac = /^darwin/.test(process.platform) -// var workspace: String = _ -// var VERIFY = "verify" -// var selectedBackend: String -// -// private var firstSettingsCheck = true -// -// private var _valid: Boolean = false -// private var _errors: Array[SettingsError] = Array() -// private var _upToDate: Boolean = false -// -// private var home = os.homedir(); -// -// def getStage(backend: BackendProperties, name: Option[String]): Option[Stage] = { -// for { -// n <- name -// res <- backend.stages.find(_.name == n) -// } yield res -// } -// -// def getStageFromSuccess(backend: BackendProperties, stage: Stage, success: Success): Stage = { -// switch (success) { -// case VerificationSuccess.ParsingFailed: -// return this.getStage(backend, stage.onParsingError); -// case VerificationSuccess.VerificationFailed: -// return this.getStage(backend, stage.onVerificationError); -// case VerificationSuccess.TypecheckingFailed: -// return this.getStage(backend, stage.onTypeCheckingError); -// case VerificationSuccess.Success: -// return this.getStage(backend, stage.onSuccess); -// } -// return null; -// } -// -// def backendEquals(a: BackendProperties, b: BackendProperties): Boolean = { -// var areEqual: Boolean = a.stages.length == b.stages.length -// areEqual = areEqual && a.name == b.name -// areEqual = areEqual && a.backend_type == b.backend_type -// areEqual = areEqual && a.timeout == b.timeout -// areEqual = areEqual && this.resolveEngine(a.engine) == this.resolveEngine(b.engine); -// a.stages.forEach((element, i) => { -// areEqual = areEqual && this.stageEquals(element, b.stages[i]); -// }); -// areEqual = areEqual && a.paths.length == b.paths.length; -// for (var i = 0; i < a.paths.length; i++) { -// areEqual = areEqual && a.paths[i] == b.paths[i]; -// } -// return areEqual; -// } -// -// def backendEquals(one: Option[BackendProperties], other: Option[BackendProperties]): Option[Boolean] = { -// for { -// a <- one -// b <- other -// } yield backendEquals(a, b) -// } -// -// private def resolveEngine(engine: String) { -// if (engine && (engine.toLowerCase() == "viperserver")) { -// return engine; -// } else { -// return "none"; -// } -// } -// -// def useViperServer(backend: BackendProperties) { -// if (!backend || !backend.engine) return false; -// return backend.engine.toLowerCase() == "viperserver"; -// } -// -// private def stageEquals(a: Stage, b: Stage): Boolean = { -// var same = a.customArguments == b.customArguments; -// same = same && a.mainMethod == b.mainMethod; -// same = same && a.name == b.name; -// same = same && a.isVerification == b.isVerification; -// same = same && a.onParsingError == b.onParsingError; -// same = same && a.onTypeCheckingError == b.onTypeCheckingError; -// same = same && a.onVerificationError == b.onVerificationError; -// same = same && a.onSuccess == b.onSuccess; -// return same; -// } -// -// def expandCustomArguments(args: String, stage: Stage, fileToVerify: String, backend: BackendProperties): String = { -// //Log.log("Command before expanding: " + args,LogLevel.LowLevelDebug); -// args = args.replace(/\s+/g, ' '); //remove multiple spaces -// args = args.replace(/\$z3Exe\$/g, '"' + this.settings.paths.z3Executable + '"'); -// args = args.replace(/\$boogieExe\$/g, '"' + this.settings.paths.boogieExecutable + '"'); -// args = args.replace(/\$mainMethod\$/g, stage.mainMethod); -// args = args.replace(/\$backendPaths\$/g, Settings.backendJars(backend)); -// args = args.replace(/\$disableCaching\$/g, (Settings.settings.viperServerSettings.disableCaching === true ? "--disableCaching" : "")); -// args = args.replace(/\$fileToVerify\$/g, '"' + fileToVerify + '"'); -// args = args.replace(/\s+/g, ' '); //remove multiple spaces -// //Log.log("Command after expanding: " + args.trim(),LogLevel.LowLevelDebug); -// -// return args.trim(); -// } -// -// def expandViperToolsPath(path: String): String = { -// if (!path) return path -// if (typeof Settings.settings.paths.viperToolsPath !== "String") { -// return path -// } -//// path = path.replace(/\$viperTools\$/g, [String>Settings.settings.paths.viperToolsPath) -// return path -// } -// -// def selectBackend(settings: ViperSettings, selectedBackend: String): BackendProperties = { -// if (selectedBackend != null) { -// Settings.selectedBackend = selectedBackend; -// } -// if (!settings || !settings.verificationBackends || settings.verificationBackends.length == 0) { -// this.selectedBackend = null; -// return null; -// } -// if (this.selectedBackend) { -//// for (var i = 0; i < settings.verificationBackends.length; i++) { -//// var backend = settings.verificationBackends[i]; -//// if (backend.name === this.selectedBackend) { -//// return backend; -//// } -//// } -// } -// this.selectedBackend = settings.verificationBackends[0].name; -// return settings.verificationBackends[0]; -// } -// -// def getBackendNames(settings: ViperSettings): Array[String] = { -// var backendNames = []; -// settings.verificationBackends.forEach((backend) => { -// backendNames.push(backend.name); -// }) -// return backendNames; -// } -// -// def getBackend(backendName: String): BackendProperties = { -// return Settings.settings.verificationBackends.find(b => { return b.name == backendName }); -// } -// -// def valid(): Boolean = { -// LanguageServerState.sendSettingsCheckedNotification({ ok: this._valid, errors: this._errors, settings: this.settings }); -// return this._valid; -// } -// -// def upToDate(): Boolean = { -// return this._upToDate; -// } -// -// private def viperServerRelatedSettingsChanged(oldSettings: ViperSettings) = { -// if (!oldSettings) return true; -// if ((Array[[String]]oldSettings.viperServerSettings.serverJars).length != (Array[[String]]this.settings.viperServerSettings.serverJars).length) -// return true; -// (Array[[String]]oldSettings.viperServerSettings.serverJars).forEach((path, index) => { -// if (path != (Array[[String]]this.settings.viperServerSettings.serverJars)[index]) { -// return true; -// } -// }) -// if (oldSettings.viperServerSettings.backendSpecificCache != this.settings.viperServerSettings.backendSpecificCache -// || oldSettings.viperServerSettings.customArguments != this.settings.viperServerSettings.customArguments -// //|| oldSettings.viperServerSettings.disableCaching != this.settings.viperServerSettings.disableCaching //no need to restart the ViperServer if only that changes -// || oldSettings.viperServerSettings.timeout != this.settings.viperServerSettings.timeout -// ) { -// return true; -// } -// Log.log("ViperServer settings did not change", LogLevel.LowLevelDebug); -// return false; -// } -// -// //tries to restart backend, -// def initiateBackendRestartIfNeeded(oldSettings: Option[ViperSettings], selectedBackend: Option[String], viperToolsUpdated: Boolean = false) { -// checkSettings(viperToolsUpdated).thenApply(() => { -// if (valid()) { -// var newBackend = null -// if (newBackend) { -// //only restart the backend after settings changed if the active backend was affected -// Log.log("check if restart needed", LogLevel.LowLevelDebug); -// val backendChanged = false //change in backend -// val mustRestartBackend = false //backend is not ready -> restart -// if (mustRestartBackend || backendChanged) { -// Log.log(`Change Backend: from ${LanguageServerState.backend ? LanguageServerState.backend.name : "No Backend"} to ${newBackend ? newBackend.name : "No Backend"}`, LogLevel.Info); -// Coordinator.backend = newBackend; -// Coordinator.files.forEach(task => task.resetLastSuccess()); -// Coordinator.sendStartBackendMessage(Coordinator.backend.name, mustRestartBackend, Settings.useViperServer(newBackend)); -// } else { -// Log.log("No need to restart backend. It is still the same", LogLevel.Debug) -// Coordinator.backend = newBackend; -// Coordinator.sendBackendReadyNotification({ -// name: Coordinator.backend.name, -// restarted: false, -// isViperServer: Settings.useViperServer(newBackend) -// }); -// } -// } else { -// Log.debug("No backend, even though the setting check succeeded."); -// } -// } else { -// Coordinator.verifier.stop(); -// } -// }); -// } -// -// private def addError(msg: String) { -// this._errors.push({ type: SettingsErrorType.Error, msg: msg }); -// } -// -// private def addErrors(errors: SettingsError[]) { -// this._errors = this._errors.concat(errors); -// } -// -// private def addWarning(msg: String) { -// this._errors.push({ type: SettingsErrorType.Warning, msg: msg }); -// } -// -// private def checkSettingsVersion(settings: AnyRef, requiredVersions: AnyRef): Array[String] = { -// var oldSettings = Array() -// //check the settings versions -// if (!requiredVersions) { -// Log.error("Getting required version failed."); -// } else { -// if (Version.createFromVersion(requiredVersions.advancedFeaturesVersion).compare(Version.createFromHash(settings.advancedFeatures.v)) > 0) { -// oldSettings.push("advancedFeatures"); -// } -// if (Version.createFromVersion(requiredVersions.javaSettingsVersion).compare(Version.createFromHash(settings.javaSettings.v)) > 0) { -// oldSettings.push("javaSettings"); -// } -// if (Version.createFromVersion(requiredVersions.viperServerSettingsVersion).compare(Version.createFromHash(settings.viperServerSettings.v)) > 0) { -// oldSettings.push("viperServerSettings"); -// } -// if (Version.createFromVersion(requiredVersions.pathSettingsVersion).compare(Version.createFromHash(settings.paths.v)) > 0) { -// oldSettings.push("paths"); -// } -// if (Version.createFromVersion(requiredVersions.userPreferencesVersion).compare(Version.createFromHash(settings.preferences.v)) > 0) { -// oldSettings.push("preferences"); -// } -// var requiredBackendVersion = Version.createFromVersion(requiredVersions.backendSettingsVersion); -// settings.verificationBackends.forEach(backend => { -// if (requiredBackendVersion.compare(Version.createFromHash(backend.v)) > 0) { -// oldSettings.push("backend " + backend.name); -// } -// }); -// } -// return oldSettings; -// } -// -// -// -// def checkSettings(viperToolsUpdated: Boolean): Future[Boolean] = { -// try { -// _valid = false; -// _errors = Array(); -// _upToDate = false; -// -// def getSettingErrorFuture: Future[Array[SettingsError]] = Future { -// val cf = LanguageServerState.client.checkIfSettingsVersionsSpecified() -// val res = cf.join() -// res -// } -// -// def hasErrors(errors: Array[SettingsError]): Boolean = { -// if (errors.nonEmpty) { -// errors.foreach(addErrors) -// true -// } else { -// false -// } -// } -// -// def getVersionsFuture(errors: Array[SettingsError]): Future[Option[Versions]] = { -// if(hasErrors(errors)) { -// Future { -// None -// } -// } else { -// Future { -// val cf = LanguageServerState.client.requestRequiredVersion() -// val res = cf.join() -// Some(res) -// } -// } -// } -// -// def useVersions(versions: Option[Versions]): Future[Boolean] = { -// Future { -// case None => -// false -// case Some(v) => -// if (firstSettingsCheck) { -// Log.log("Extension Version: " + v.extensionVersion + " - " + Version.hash(requiredVersions.extensionVersion), LogLevel.LowLevelDebug) -// firstSettingsCheck = false; -// } -// var settings = Settings.settings; -// var oldSettings: Array[String] = this.checkSettingsVersion(settings, v); -// var defaultSettings = v.defaultSettings; -// -// if (oldSettings.nonEmpty) { -// var affectedSettings = oldSettings.length < 10 ? "(" + oldSettings.join(", ") + ")": "(" + oldSettings.length + ")"; -// addError("Old viper settings detected: " + affectedSettings + " please replace the old settings with the new default settings."); -// false -// } else { -// _upToDate = true; -// //Check viperToolsProvider -// settings.preferences.viperToolsProvider = this.checkPlatformDependentUrl(settings.preferences.viperToolsProvider); -// -// //Check Paths -// //check viperToolsPath -// var resolvedPath: ResolvedPath = this.checkPath(settings.paths.viperToolsPath, "Path to Viper Tools:", false, true, true); -// settings.paths.viperToolsPath = resolvedPath.path; -// if (!resolvedPath.exists) { -// if (!viperToolsUpdated) { -// //Automatically install the Viper tools -// LanguageServerState.updateViperTools(true); -// reject(); // in this case we do not want to continue restarting the backend, -// //the backend will be restarted after the update -// } else { -// resolve(false); -// } -// return; -// } -// -// //check z3 Executable -// settings.paths.z3Executable = this.checkPath(settings.paths.z3Executable, "z3 Executable:", true, true, true).path; -// //check boogie executable -// settings.paths.boogieExecutable = this.checkPath(settings.paths.boogieExecutable, `Boogie Executable: (If you don't need boogie, set it to "")`, true, true, true).path; -// -// //check backends -// if (!settings.verificationBackends || settings.verificationBackends.length == 0) { -// settings.verificationBackends = defaultSettings["viperSettings.verificationBackends"].default; -// } else { -// defaultSettings["viperSettings.verificationBackends"].default.forEach(defaultBackend => { -// var customBackend = settings.verificationBackends.filter(backend => backend.name == defaultBackend.name)[0]; -// if (customBackend) { -// //Merge the backend with the default backend -// this.mergeBackend(customBackend, defaultBackend); -// } else { -// //Add the default backend if there is none with the same name -// settings.verificationBackends.push(defaultBackend); -// } -// }) -// } -// Settings.checkBackends(settings.verificationBackends); -// -// //check ViperServer related settings -// var viperServerRequired = settings.verificationBackends.some(elem => this.useViperServer(elem)); -// if (viperServerRequired) { -// //check viperServer path -// settings.viperServerSettings.serverJars = this.checkPaths(settings.viperServerSettings.serverJars, "viperServerPath:"); -// if (this.viperServerJars().trim().length == 0) { -// this.addError("Missing viper server jars at paths: " + JSON.Stringify(settings.viperServerSettings.serverJars)) -// } -// //check viperServerTimeout -// settings.viperServerSettings.timeout = this.checkTimeout(settings.viperServerSettings.timeout, "viperServerSettings:"); -// //check the customArguments -// } -// -// //no need to check preferences -// //check java settings -// if (!settings.javaSettings.customArguments) { -// this.addError("The customArguments are missing in the java settings"); -// } -// -// //checks done -// this._valid = !this._errors.some(error => error. -// type == -// SettingsErrorType.Error -// ); //if there is no error -> valid -// if (this._valid) { -// Log.log("The settings are ok", LogLevel.Info); -// resolve(true); -// } else { -// resolve(false); -// } -// } -// } -// } -// } -// -// for { -// errors <- getSettingErrorFuture -// versions <- getVersionsFuture(errors) -// result <- useVersions(versions) -// } yield result -// -// -// -// } -// -// }) -// } catch { -// Log.error("Error checking settings: " + e) -// resolve(false) -// } -// }) -// } -// -// private def mergeBackend(custom: Backend, def: Backend) = { -// if (!custom || !def || custom.name != def.name) return; -// if (!custom.paths) custom.paths = def.paths; -// if (!custom.stages) custom.stages = def.stages -// else this.mergeStages(custom.stages, def.stages); -// if (!custom.timeout) custom.timeout = def.timeout; -// if (!custom.engine || custom.engine.length == 0) custom.engine = def.engine; -// if (!custom.type || custom.type.length == 0) custom.type = def.type; -// } -// -// private def mergeStages(custom: Stage[], defaultStages: Stage[]) = { -// defaultStages.forEach(def => { -// var cus = custom.filter(stage => stage.name == def.name)[0]; -// if (cus) { -// //merge -// if (cus.customArguments === undefined) cus.customArguments = def.customArguments; -// if (!cus.mainMethod) cus.mainMethod = def.mainMethod; -// if (cus.isVerification === undefined) cus.isVerification = def.isVerification; -// } else { -// custom.push(def); -// } -// }); -// } -// -// private def checkPlatformDependentUrl(url: String | PlatformDependentURL): String = { -// var StringURL = null; -// if (url) { -// if (typeof url === "String") { -// StringURL = url; -// } else { -// if (Settings.isLinux) { -// StringURL = url.linux; -// } else if (Settings.isMac) { -// StringURL = url.mac; -// } else if (Settings.isWin) { -// StringURL = url.windows; -// } else { -// Log.error("Operation System detection failed, Its not Mac, Windows or Linux"); -// } -// } -// } -// if (!StringURL || StringURL.length == 0) { -// this.addError("The viperToolsProvider is missing in the preferences"); -// } -// //TODO: check url format -// return StringURL; -// } -// -// private def checkPaths(paths: (String | Array[String] | PlatformDependentPath | PlatformDependentListOfPaths), prefix: String): Array[String] = { -// //Log.log("checkPaths(" + JSON.Stringify(paths) + ")", LogLevel.LowLevelDebug); -// var result: Array[String] = [] -// var StringPaths: Array[String] = [] -// if (!paths) { -// this.addError(prefix + " paths are missing"); -// } else if (typeof paths === "String") { -// StringPaths.push(paths) -// } else if (paths instanceof Array) { -// paths.forEach(path => { -// if (typeof path === "String") { -// StringPaths.push(path) -// } -// }) -// } else { -// var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>paths; -// if (Settings.isLinux) { -// return this.checkPaths(platformDependentPath.linux, prefix); -// } else if (Settings.isMac) { -// return this.checkPaths(platformDependentPath.mac, prefix); -// } else if (Settings.isWin) { -// return this.checkPaths(platformDependentPath.windows, prefix); -// } else { -// Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); -// } -// return result; -// } -// -// if (StringPaths.length == 0) { -// this.addError(prefix + ' path has wrong type: expected: String | Array[String] | {windows:(String|Array[String]), mac:(String|Array[String]), linux:(String|Array[String])}, found: ' + typeof paths + " at path: " + JSON.Stringify(paths)); -// } -// -// //resolve the paths -// StringPaths = StringPaths.map(StringPath => { -// var resolvedPath = Settings.resolvePath(StringPath, false); -// if (!resolvedPath.exists) { -// this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); -// } -// return resolvedPath.path -// }); -// if (StringPaths.length == 0) { -// this.addError(prefix + ' no file found at at path: ' + JSON.Stringify(paths)); -// } -// //Log.log("checkPaths result: (" + JSON.Stringify(StringPaths) + ")", LogLevel.LowLevelDebug); -// return StringPaths; -// } -// -// private def checkPath(path: (String | PlatformDependentPath), prefix: String, executable: Boolean, allowPlatformDependentPath: Boolean, allowStringPath: Boolean = true, allowMissingPath = false): ResolvedPath = { -// if (!path) { -// if (!allowMissingPath) this.addError(prefix + " path is missing"); -// return { path: null, exists: false }; -// } -// var StringPath: String; -// if (typeof path === "String") { -// if (!allowStringPath) { -// this.addError(prefix + ' path has wrong type: expected: {windows:String, mac:String, linux:String}, found: ' + typeof path); -// return { path: StringPath, exists: false }; -// } -// StringPath = [String>path; -// } else { -// if (!allowPlatformDependentPath) { -// this.addError(prefix + ' path has wrong type: expected: String, found: ' + typeof path + " at path: " + JSON.Stringify(path)); -// return { path: null, exists: false }; -// } -// var platformDependentPath: PlatformDependentPath = [PlatformDependentPath>path; -// if (Settings.isLinux) { -// StringPath = platformDependentPath.linux; -// } else if (Settings.isMac) { -// StringPath = platformDependentPath.mac; -// } else if (Settings.isWin) { -// StringPath = platformDependentPath.windows; -// } else { -// Log.debug("Operation System detection failed, Its not Mac, Windows or Linux"); -// } -// } -// -// if (!StringPath || StringPath.length == 0) { -// if (!allowMissingPath) { -// this.addError(prefix + ' path has wrong type: expected: String' + (executable ? ' or {windows:String, mac:String, linux:String}' : "") + ', found: ' + typeof path + " at path: " + JSON.Stringify(path)); -// } -// return { path: StringPath, exists: false }; -// } -// var resolvedPath = Settings.resolvePath(StringPath, executable); -// if (!resolvedPath.exists) { -// this.addError(prefix + ' path not found: "' + StringPath + '"' + (resolvedPath.path != StringPath ? ' which expands to "' + resolvedPath.path + '"' : "") + (" " + (resolvedPath.error || ""))); -// } -// return resolvedPath; -// } -// -// private def checkBackends(backends: BackendProperties[]) { -// //Log.log("Checking backends...", LogLevel.Debug); -// if (!backends || backends.length == 0) { -// this.addError("No backend detected, specify at least one backend"); -// return; -// } -// -// var backendNames: Set[String> = new Set[String>(); -// -// for (var i = 0; i < backends.length; i++) { -// var backend = backends[i]; -// if (!backend) { -// this.addError("Empty backend detected"); -// } -// else if (!backend.name || backend.name.length == 0) {//name there? -// this.addWarning("Every backend setting should have a name."); -// backend.name = "backend" + (i + 1); -// } -// var backendName = "Backend " + backend.name + ":"; -// //check for duplicate backends -// if (backendNames.has(backend.name)) this.addError("Dublicated backend name: " + backend.name); -// backendNames.add(backend.name); -// -// //check stages -// if (!backend.stages || backend.stages.length == 0) { -// this.addError(backendName + " The backend setting needs at least one stage"); -// continue; -// } -// -// backend.engine = this.resolveEngine(backend.engine); -// //check engine and type -// if (this.useViperServer(backend) && !ViperServerService.isSupportedType(backend.type)) { -// this.addError(backendName + "the backend type " + backend.type + " is not supported, try one of these: " + ViperServerService.supportedTypes); -// } -// -// var stages: Set[String> = new Set[String>(); -// var verifyStageFound = false; -// for (var i = 0; i < backend.stages.length; i++) { -// var stage: Stage = backend.stages[i]; -// if (!stage) { -// this.addError(backendName + " Empty stage detected"); -// } -// else if (!stage.name || stage.name.length == 0) { -// this.addError(backendName + " Every stage needs a name."); -// } else { -// var backendAndStage = backendName + " Stage: " + stage.name + ":"; -// //check for duplicated stage names -// if (stages.has(stage.name)) -// this.addError(backendName + " Duplicated stage name: " + stage.name); -// stages.add(stage.name); -// //check mainMethod -// if (!stage.mainMethod || stage.mainMethod.length == 0) -// this.addError(backendAndStage + " Missing mainMethod"); -// //check customArguments -// if (!stage.customArguments) { -// this.addError(backendAndStage + " Missing customArguments"); -// continue; -// } -// } -// } -// for (var i = 0; i < backend.stages.length; i++) = { -// var stage: Stage = backend.stages[i]; -// var BackendMissingStage = backendName + ": Cannot find stage " + stage.name; -// if (stage.onParsingError && stage.onParsingError.length > 0 && !stages.has(stage.onParsingError)) -// this.addError(BackendMissingStage + "'s onParsingError stage " + stage.onParsingError); -// if (stage.onTypeCheckingError && stage.onTypeCheckingError.length > 0 && !stages.has(stage.onTypeCheckingError)) -// this.addError(BackendMissingStage + "'s onTypeCheckingError stage " + stage.onTypeCheckingError); -// if (stage.onVerificationError && stage.onVerificationError.length > 0 && !stages.has(stage.onVerificationError)) -// this.addError(BackendMissingStage + "'s onVerificationError stage " + stage.onVerificationError); -// if (stage.onSuccess && stage.onSuccess.length > 0 && !stages.has(stage.onSuccess)) -// this.addError(BackendMissingStage + "'s onSuccess stage " + stage.onSuccess); -// } -// -// //check paths -// if (!backend.paths || backend.paths.length == 0) { -// if (!this.useViperServer(backend)) this.addError(backendName + " The backend setting needs at least one path"); -// } else { -// if (typeof backend.paths == 'String') { -// var temp = backend.paths; -// backend.paths = [temp]; -// } -// for (var i = 0; i < backend.paths.length; i++) { -// //extract environment variable or leave unchanged -// backend.paths[i] = Settings.checkPath(backend.paths[i], backendName, false, false).path; -// } -// } -// -// //check verification timeout -// backend.timeout = this.checkTimeout(backend.timeout, "Backend " + backendName + ":"); -// } -// return null; -// } -// -// private def checkTimeout(timeout: number, prefix: String): number = { -// if (!timeout || (timeout && timeout <= 0)) { -// if (timeout && timeout < 0) { -// this.addWarning(prefix + " The timeout of " + timeout + " is interpreted as no timeout."); -// } -// return null; -// } -// return timeout; -// } -// -// def backendJars(backend: BackendProperties): String = { -// var jarFiles = this.getAllJarsInPaths(backend.paths, false); -// return this.buildDependencyString(jarFiles); -// } -// -// def viperServerJars(): String = { -// var jarFiles = this.getAllJarsInPaths(Array[[String]]this.settings.viperServerSettings.serverJars, false); -// return this.buildDependencyString(jarFiles); -// } -// -// def buildDependencyString(jarFiles: Array[String]): String = { -// var dependencies = ""; -// var concatenationSymbol = Settings.isWin ? ";" : ":"; -// if (jarFiles.length > 0) { -// dependencies = dependencies + concatenationSymbol + '"' + jarFiles.join('"' + concatenationSymbol + '"') + '"' -// } -// return dependencies; -// } -// -// def getAllJarsInPaths(paths: Array[String], recursive: Boolean): Array[String] = { -// var result: Array[String] = []; -// try { -// paths.forEach(path => { -// if (fs.lstatSync(path).isDirectory()) { -// var files = fs.readdirSync(path); -// var folders = [] -// files.forEach(child => { -// child = pathHelper.join(path, child) -// if (!fs.lstatSync(child).isDirectory()) { -// //child is a file -// if (this.isJar(child)) { -// //child is a jar file -// result.push(child); -// } -// } else { -// folders.push(child); -// } -// }) -// if (recursive) { -// result.push(...this.getAllJarsInPaths(folders, recursive)); -// } -// } else { -// if (this.isJar(path)) { -// result.push(path) -// } -// } -// }) -// } catch (e) { -// Log.error("Error getting all Jars in Paths: " + e); -// } -// return result; -// } -// -// private def isJar(file: String): Boolean = { -// return file ? file.trim().endsWith(".jar") : false; -// } -// -// private def extractEnvVars(path: String): ResolvedPath = { -// if (path && path.length > 2) { -// while (path.indexOf("%") >= 0) { -// var start = path.indexOf("%") -// var end = path.indexOf("%", start + 1); -// if (end < 0) { -// return { path: path, exists: false, error: "unbalanced % in path: " + path }; -// } -// var envName = path.subString(start + 1, end); -// var envValue = process.env[envName]; -// if (!envValue) { -// return { path: path, exists: false, error: "environment variable " + envName + " used in path " + path + " is not set" }; -// } -// if (envValue.indexOf("%") >= 0) { -// return { path: path, exists: false, error: "environment variable: " + envName + " must not contain %: " + envValue }; -// } -// path = path.subString(0, start - 1) + envValue + path.subString(end + 1, path.length); -// } -// } -// return { path: path, exists: true }; -// } -// -// private def resolvePath(path: String, executable: Boolean): ResolvedPath = { -// try { -// if (!path) { -// return { path: path, exists: false }; -// } -// path = path.trim(); -// -// //expand internal variables -// var resolvedPath = this.expandViperToolsPath(path); -// //handle env Vars -// var envVarsExtracted = this.extractEnvVars(resolvedPath); -// if (!envVarsExtracted.exists) return envVarsExtracted; -// resolvedPath = envVarsExtracted.path; -// -// //handle files in Path env var -// if (resolvedPath.indexOf("/") < 0 && resolvedPath.indexOf("\\") < 0) { -// //its only a filename, try to find it in the path -// var pathEnvVar: String = process.env.PATH; -// if (pathEnvVar) { -// var pathList: Array[String] = pathEnvVar.split(Settings.isWin ? ";" : ":"); -// for (var i = 0; i < pathList.length; i++) { -// var pathElement = pathList[i]; -// var combinedPath = this.toAbsolute(pathHelper.join(pathElement, resolvedPath)); -// var exists = this.exists(combinedPath, executable); -// if (exists.exists) return exists; -// } -// } -// } else { -// //handle absolute and relative paths -// if (this.home) { -// resolvedPath = resolvedPath.replace(/^~($|\/|\\)/, `${this.home}$1`); -// } -// resolvedPath = this.toAbsolute(resolvedPath); -// return this.exists(resolvedPath, executable); -// } -// return { path: resolvedPath, exists: false }; -// } catch (e) { -// Log.error("Error resolving path: " + e); -// } -// } -// -// private def exists(path: String, executable: Boolean): ResolvedPath = { -// try { -// fs.accessSync(path); -// return { path: path, exists: true }; -// } catch (e) { } -// if (executable && this.isWin && !path.toLowerCase().endsWith(".exe")) { -// path += ".exe"; -// //only one recursion at most, because the ending is checked -// return this.exists(path, executable); -// } -// return { path: path, exists: false } -// } -// -// private def toAbsolute(path: String): String = { -// return pathHelper.resolve(pathHelper.normalize(path)); -// } - } -// -//class Version(versionNumbers: Array[Int] = Array(0, 0, 0)) { -// private val _key = "VdafSZVOWpe"; -// -// var versionNumbers: Array[Int] = Array(0, 0, 0); -// -// def createFromVersion(version: Version): Version = { -// try { -// if (version != null) { -// if (/\d+(\.\d+)+/.test(version)) { -// return new Version(version.split(".").map(x => Number.parseInt(x))) -// } -// } -// } catch { -// case e: Throwable => Log.debug("Error creating version from Version: " + e); -// } -// -// return new Version(); -// } -// -// def createFromHash(hash) = { -// try { -// if (hash) { -// var version = this.decrypt(hash, _key); -// //Log.log("hash: " + hash + " decrypted version: " + version, LogLevel.LowLevelDebug); -// return this.createFromVersion(version); -// } -// } catch { -// case e: Throwable => Log.debug("Error creating version from hash: " + e); -// } -// return new Version(); -// } -// -// private def encrypt(msg: String, key: String): String = { -// var res: String = "" -// var parity: number = 0; -// for (var i = 0; i < msg.length; i++) { -// var keyChar: number = key.charCodeAt(i % key.length); -// //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); -// var char: number = msg.charCodeAt(i); -// //Log.log("char " + msg.charAt(i) + " charCode: " + char,LogLevel.LowLevelDebug); -// var cypher: number = (char ^ keyChar) -// parity = (parity + cypher % (16 * 16)) % (16 * 16); -// //Log.log("cypher " + (char ^ keyChar).toString() + " hex: "+ cypher,LogLevel.LowLevelDebug); -// res += this.pad(cypher); -// } -// return res + this.pad(parity); -// } -// -// private def pad(n: number): String = { -// var s = n.toString(16); -// return (s.length == 1 ? "0" : "") + s; -// } -// -// private def decrypt(cypher: String, key: String): String = { -// //Log.log("decrypt",LogLevel.LowLevelDebug); -// var res: String = "" -// var parity: number = 0; -// if (!cypher || cypher.length < 2 || cypher.length % 2 != 0) { -// return ""; -// } -// for (var i = 0; i < cypher.length - 2; i += 2) { -// var keyChar: number = key.charCodeAt((i / 2) % key.length); -// //Log.log("keyChar " + key.charAt(i % key.length),LogLevel.LowLevelDebug); -// var char: number = (16 * parseInt(cypher.charAt(i), 16)) + parseInt(cypher.charAt(i + 1), 16) -// parity = (parity + char % (16 * 16)) % (16 * 16); -// //Log.log("char " + char,LogLevel.LowLevelDebug); -// //Log.log("encChar " + String.fromCharCode(char ^ keyChar) + " charCode: "+(char ^ keyChar),LogLevel.LowLevelDebug); -// res += String.fromCharCode(char ^ keyChar) -// } -// if (parity != (16 * parseInt(cypher.charAt(cypher.length - 2), 16)) + parseInt(cypher.charAt(cypher.length - 1), 16)) { -// return "" -// } else { -// return res -// } -// } -// -// toString(): String { -// return this.versionNumbers.join("."); -// } -// -// def testhash() { -// var s = "1.0.0"; -// var en = this.encrypt(s, Version.Key); -// var de = this.decrypt(en, Version.Key); -// Log.log("Hash Test: " + s + " -> " + en + " -> " + de, LogLevel.LowLevelDebug) -// } -// -// def hash(version: String): String = { -// var hash = this.encrypt(version, Version.Key); -// //Log.log("version: " + version + " hash: " + hash, LogLevel.LowLevelDebug); -// return hash; -// } -// -// //1: this is larger, -1 other is larger -// def compare(other: Version): Int = { -// for (var i = 0; i < this.versionNumbers.length; i++) { -// if (i >= other.versionNumbers.length) return 1; -// if (this.versionNumbers[i] > other.versionNumbers[i]) return 1; -// if (this.versionNumbers[i] < other.versionNumbers[i]) return -1; -// } -// return this.versionNumbers.length < other.versionNumbers.length ? -1 : 0; -// } -//} \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 5b2cb5a..c1d5437 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -18,9 +18,8 @@ import viper.server.core.{ViperCache, ViperCoreServer} import viper.server.frontends.lsp.VerificationState.Stopped import viper.server.utility.AstGenerator import viper.server.vsi.VerificationProtocol.Stop -import viper.server.vsi.{JobID, VerificationProtocol, VerificationServer} +import viper.server.vsi.{JobID, VerificationServer} import viper.silver.ast.Program -import viper.silver.reporter.PongMessage import scala.compat.java8.FutureConverters._ import scala.concurrent.Future diff --git a/src/test/scala/ViperServerTests.scala b/src/test/scala/ViperServerTests.scala index 486e757..59fb906 100644 --- a/src/test/scala/ViperServerTests.scala +++ b/src/test/scala/ViperServerTests.scala @@ -24,9 +24,9 @@ class ViperServerSpec extends WordSpec with Matchers with ScalatestRouteTest { implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() implicit val requestTimeput: RouteTestTimeout = RouteTestTimeout(10.second dilated) - ViperServerRunner.startHttpServer(Array()) + HttpServerRunner.main(Array()) - private val _routsUnderTest = ViperServerRunner.viperServerHTTP.routes() + private val _routsUnderTest = HttpServerRunner.viperServerHTTP.routes() def printRequestResponsePair(req: String, res: String): Unit = { println(s">>> ViperServer test request `$req` response in the following response: $res") From c31449861dc6b84eb439f2ebd0297e253a0fb91e Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 21 Oct 2020 21:58:11 +0200 Subject: [PATCH 16/79] removed legacy code --- .../scala/viper/server/HttpServerRunner.scala | 6 +- src/main/scala/viper/server/ViperServer.scala | 1 - .../frontends/http/ViperHttpServer.scala | 2 +- .../viper/server/frontends/lsp/Common.scala | 3 +- .../server/frontends/lsp/Coordinator.scala | 7 +- .../server/frontends/lsp/DataProtocol.scala | 19 +++-- .../server/frontends/lsp/FileManager.scala | 73 +++---------------- .../viper/server/frontends/lsp/IdeLog.scala | 11 +-- .../viper/server/frontends/lsp/Receiver.scala | 35 +++++---- src/main/scala/viper/server/vsi/HTTP.scala | 2 +- .../viper/server/vsi/VerificationServer.scala | 6 +- src/test/scala/ViperServerTests.scala | 2 +- 12 files changed, 50 insertions(+), 117 deletions(-) diff --git a/src/main/scala/viper/server/HttpServerRunner.scala b/src/main/scala/viper/server/HttpServerRunner.scala index bb5624e..1f71aaa 100644 --- a/src/main/scala/viper/server/HttpServerRunner.scala +++ b/src/main/scala/viper/server/HttpServerRunner.scala @@ -9,13 +9,13 @@ import viper.server.frontends.http.ViperHttpServer object HttpServerRunner { - var viperServerHTTP: ViperHttpServer = _ + var viperServerHttp: ViperHttpServer = _ /** Start VCS in HTTP mode. * */ def startHttpServer(args: Array[String]): Unit = { - viperServerHTTP = new ViperHttpServer(args) - viperServerHTTP.start() + viperServerHttp = new ViperHttpServer(args) + viperServerHttp.start() } def main(args: Array[String]): Unit = { diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index 16bb269..9e02147 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -40,7 +40,6 @@ object ViperServerRunner { Coordinator.port = port Coordinator.url = localAddress - val server: CustomReceiver = new CustomReceiver() val launcher = Launcher.createLauncher(server, classOf[IdeLanguageClient], socket.getInputStream, socket.getOutputStream) server.connect(launcher.getRemoteProxy) diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 3ed5460..fd785d8 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -30,7 +30,7 @@ import viper.silver.reporter.Message import scala.util.{Failure, Success, Try} class ViperHttpServer(_args: Array[String]) - extends ViperCoreServer(_args) with VerificationServerHTTP { + extends ViperCoreServer(_args) with VerificationServerHttp { override def start(): Unit = { _config = new ViperConfig(_args) diff --git a/src/main/scala/viper/server/frontends/lsp/Common.scala b/src/main/scala/viper/server/frontends/lsp/Common.scala index 3b59e42..842baa6 100644 --- a/src/main/scala/viper/server/frontends/lsp/Common.scala +++ b/src/main/scala/viper/server/frontends/lsp/Common.scala @@ -16,8 +16,7 @@ import org.eclipse.lsp4j.{Position, Range} object Common { - //TODO Get this from client programatically - var viperFileEndings: Array[String] = Array("vpr", ".sil") + var viperFileEndings: Array[String] = Array("vpr", "sil") def uriFromString(uri: String): URI = { URI.create(uri) diff --git a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala index 45ddcfa..a83f65e 100644 --- a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala +++ b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala @@ -69,12 +69,7 @@ object Coordinator { try { client.notifyStateChanged(params) } catch { - case e: Throwable => println(e) + case e: Throwable => Log.debug("Error while changing state: ", e) } } - -// def sendStepsAsDecorationOptions(decorations: StepsAsDecorationOptionsResult) = { -// Log.log("Update the decoration options (" + decorations.decorationOptions.length + ")", LogLevel.Debug) -// client.stepsAsDecorationOptions(decorations) -// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala index 519076f..da326d0 100644 --- a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala @@ -16,7 +16,7 @@ object VerificationSuccess extends Enumeration { val NA, Success, ParsingFailed, TypecheckingFailed = Value val VerificationFailed = Value // Manually aborted verification val Aborted = Value // Caused by internal error - val Error = Value // Caused by veification taking too long + val Error = Value // Caused by verification taking too long val Timeout = Value } import viper.server.frontends.lsp.VerificationSuccess._ @@ -42,13 +42,13 @@ import viper.server.frontends.lsp.SettingsErrorType._ object LogLevel extends Enumeration { type LogLevel = Value - val None = Value // No output - val Default = Value // Only verification specific output - val Info = Value // Some info about internal state, critical errors - val Verbose = Value // More info about internal state - val Debug = Value // Detailed information about internal state, non critical errors - val LowLevelDebug = Value // all output of used tools is written to logFile, -} // some of it also to the console + val None = Value // No output + val Default = Value // Only verification specific output + val Info = Value // Some info about internal state, critical errors + val Verbose = Value // More info about internal state + val Debug = Value // Detailed information about internal state, non critical errors + val LowLevelDebug = Value // all output of used tools is written to logFile, some of it also to the console +} object BackendOutputType { val Start = "Start" @@ -120,8 +120,7 @@ case class BackendOutput( nofFunctions: Int = -1, //for End time: Long = -1, //for Error file: String = null, - errors: Array[Error] = null, //for Outlin -// members: Array[Member] = null, //for Definitions + errors: Array[Error] = null, //for Outline definitions: Array[Definition] = null) //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/main/scala/viper/server/frontends/lsp/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala index 76f87e2..492f23e 100644 --- a/src/main/scala/viper/server/frontends/lsp/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -25,7 +25,7 @@ import scala.collection.JavaConverters._ import scala.collection.mutable.ArrayBuffer class FileManager(file_uri: String) { - // file under verification + // File information var uri: URI = URI.create(file_uri) var path: Path = Paths.get(uri) var filename: String = path.getFileName.toString @@ -43,16 +43,13 @@ class FileManager(file_uri: String) { var jid: Int = -1 var time: Long = 0 var diagnostics: ArrayBuffer[Diagnostic] = _ -// var verifiables: Array[Verifiable] = _ var parsingCompleted: Boolean = false var typeCheckingCompleted: Boolean = false var backendType: String = _ var progress: Progress = _ -// var shownExecutionTrace: Array[ExecutionTrace] = _ var symbolInformation: ArrayBuffer[SymbolInformation] = ArrayBuffer() var definitions: ArrayBuffer[lsp.Definition] = ArrayBuffer() - //working variables private var lines: Array[String] = Array() private var wrongFormat: Boolean = false private var partialData: String = "" @@ -81,7 +78,6 @@ class FileManager(file_uri: String) { } time = 0 resetDiagnostics() -// verifiables = Array() parsingCompleted = true typeCheckingCompleted = true internalErrorMessage = "" @@ -102,30 +98,23 @@ class FileManager(file_uri: String) { }) } - def startStageProcess(fileToVerify: String): Option[String] = { -// def startStageProcess(stage: Stage, fileToVerify: String): Option[String] = { + + def getVerificationCommand(fileToVerify: String): Option[String] = { try { - Log.lowLevel("Start Stage Process") - Some(getStageCommand(fileToVerify, null)) + val args: String = getViperBackendClassName() + s" $fileToVerify" + Log.debug(args) + Some(args) } catch { case e: Throwable => - Log.debug("Error starting stage process: " + e) + Log.debug("Error finding backend: ", e) None } } - def getStageCommand(fileToVerify: String, stage: Stage): String = { - val args: String = getViperBackendClassName(stage) + s" $fileToVerify" -// val command = Settings.expandCustomArguments(args, stage, fileToVerify, Coordinator.backend) - Log.debug(args) - args - } - - def getViperBackendClassName(stage: Stage): String = { + def getViperBackendClassName(): String = { Coordinator.backend.backend_type match { case "Silicon" => "silicon" case "Carbon" => "carbon" -// case "other" => stage.mainMethod case _ => throw new Error(s"Invalid verification backend value. " + s"Possible values are [silicon | carbon | other] " + s"but found ${Coordinator.backend}") @@ -136,17 +125,6 @@ class FileManager(file_uri: String) { prepareVerification() this.manuallyTriggered = manuallyTriggered - //TODO this should work with precisely one stage - //This should have exactly one stage -// if (Coordinator.backend.stages == null || Coordinator.backend.stages.head == null) { -//// Log.debug("backend " + Coordinator.backend.name + " has no " + Settings.VERIFY + " stage, even though the settigns were checked.") -// println("no stage found!") -// Log.debug("Should have exactly one stage") -// return false -// } -// val stage = Coordinator.backend.stages.head - -// Coordinator.executedStages.append(stage) Log.info(s"verify $filename") Log.info(s"${Coordinator.backend.name} verification started") @@ -155,8 +133,7 @@ class FileManager(file_uri: String) { Coordinator.sendStateChangeNotification(params, Some(this)) println("state change params sent") -// val command = startStageProcess(stage, path).getOrElse(return false) - val command = startStageProcess(path.toString).getOrElse(return false) + val command = getVerificationCommand(path.toString).getOrElse(return false) Log.info(s"Successfully generated command: $command") val handle = Coordinator.verifier.verify(command) jid = handle.id @@ -194,7 +171,7 @@ class FileManager(file_uri: String) { case _ => SymbolKind.Enum } val info: SymbolInformation = new SymbolInformation(m.name, kind, location) - task.symbolInformation.append(info) + symbolInformation.append(info) }) case ProgramDefinitionsReport(defs) => definitions = ArrayBuffer() @@ -218,7 +195,6 @@ class FileManager(file_uri: String) { definitions.append(definition) }) case StatisticsReport(m, f, p, _, _) => -// TODO: pass in task (probably as props to actor). progress = new Progress(p, f, m) val params = StateChangeParams(VerificationRunning.id, progress = 0, filename = filename) Coordinator.sendStateChangeNotification(params, Some(task)) @@ -263,7 +239,6 @@ class FileManager(file_uri: String) { VerificationRunning.id, filename = filename, nofErrors = diagnostics.length, uri = file_uri, diagnostics = diagnostics.toArray) Coordinator.sendStateChangeNotification(params, Some(task)) - //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) }) case OverallSuccessMessage(_, verificationTime) => state = VerificationReporting @@ -275,7 +250,6 @@ class FileManager(file_uri: String) { completionHandler(0) case m: Message => Coordinator.client.notifyUnhandledViperServerMessage(m.toString, 2) case e: Throwable => - //receiving an error means the promise can be finalized with failure. } } @@ -308,7 +282,6 @@ class FileManager(file_uri: String) { var params: StateChangeParams = null var success = NA - // val isVerifyingStage = Coordinator.stage.getOrElse(return).isVerification val isVerifyingStage = true //do we need to start a followUp Stage? @@ -318,9 +291,6 @@ class FileManager(file_uri: String) { partialData = "" } - // Send the computed diagnostics to VSCode. - //Server.sendDiagnostics({ uri: this.fileUri, diagnostics: this.diagnostics }) - //inform client about postProcessing success = determineSuccess(code) params = StateChangeParams(PostProcessing.id, filename = filename) @@ -356,27 +326,4 @@ class FileManager(file_uri: String) { Log.debug("Error handling verification completion: ", e) } } - -// private def startVerificationTimeout(verificationCount: Int) = { -// if (Coordinator.backend.timeout > 0) { -// Log.lowLevel("Set verification timeout to " + Coordinator.backend.timeout) -// -// def timeout_callback(): Unit = { //aborts the verification on time out -// if (is_running && this.verificationCount == verificationCount) { -// Log.hint("The verification timed out after " + Coordinator.backend.timeout + "ms") -// abortVerification().thenAccept(() => { -// val params = StateChangeParams(Ready, verificationCompleted = false, -// success = Timeout, verificationNeeded = false, -// uri = fileUri) -// Coordinator.sendStateChangeNotification(params, Some(this)) -// }) -// } -// is_running = false -// } -// //TODO find timeout mechanism -// setTimeout(timeout_callback(), Coordinator.backend.timeout) -// } else { -// Log.lowLevel("No verification timeout set") -// } -// } } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/IdeLog.scala b/src/main/scala/viper/server/frontends/lsp/IdeLog.scala index f791213..77c8e92 100644 --- a/src/main/scala/viper/server/frontends/lsp/IdeLog.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLog.scala @@ -11,6 +11,7 @@ package viper.server.frontends.lsp import viper.server.frontends.lsp.LogLevel._ object Log { + def log(message: String, logLevel: LogLevel) = { require(message != null && logLevel != null) Coordinator.client.notifyLog(message, logLevel.id) @@ -49,16 +50,6 @@ object Log { } } -// -> ?? -// def logOutput(process: child_process.ChildProcess, label: String) = { -// process.stdout.on('data', (data: String) => { -// Log.logWithOrigin(label, data, LogLevel.LowLevelDebug) -// }) -// process.stdout.on('data', (data: String) => { -// Log.logWithOrigin(label + " error", data, LogLevel.LowLevelDebug) -// }) -// } - def hint(message: String, showSettingsButton: Boolean = false, showViperToolsUpdateButton: Boolean = false) = { Coordinator.client.notifyHint(S2C_Commands.Hint, Hint(message, showSettingsButton, showViperToolsUpdateButton )) } diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index 9cbb925..397c873 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -25,7 +25,7 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonRequest("initialize") def onInitialize(params: InitializeParams): CFuture[InitializeResult] = { - println("initialize") + Log.info("initialize") val capabilities = new ServerCapabilities() capabilities.setTextDocumentSync(TextDocumentSyncKind.Full) @@ -33,14 +33,15 @@ abstract class StandardReceiver extends LanguageClientAware { capabilities.setDocumentSymbolProvider(true) CFuture.completedFuture(new InitializeResult(capabilities)) } + @JsonNotification("initialized") def onInitialized(params: InitializedParams): Unit = { - println("initialized") + Log.info("initialized") } @JsonNotification("textDocument/didOpen") def onDidOpenDocument(params: DidOpenTextDocumentParams): Unit = { - println("On opening document") + Log.info("On opening document") try { val uri: String = params.getTextDocument.getUri Common.isViperSourceFile(uri).thenAccept(isViperFile => { @@ -61,7 +62,7 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonNotification("workspace/didChangeConfiguration") def onDidChangeConfig(params: DidChangeConfigurationParams): Unit = { - println("On config change") + Log.info("On config change") try { Log.lowLevel("check if restart needed") if (Coordinator.verifier == null) { @@ -79,7 +80,7 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonNotification("textDocument/didChange") def onDidChangeDocument(params: DidChangeTextDocumentParams): Unit = { - println("On changing document") + Log.info("On changing document") val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) val manager: FileManager = manager_opt.getOrElse(return) manager.symbolInformation = ArrayBuffer() @@ -88,6 +89,7 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonRequest("textDocument/documentSymbol") def onGetDocumentSymbol(params: DocumentSymbolParams): CFuture[Array[SymbolInformation]] = { + Log.info("On getting document symbol") var symbolInfo_list: Array[SymbolInformation] = Array() val manager_opt = Coordinator.files.get(params.getTextDocument.getUri) val manager = manager_opt.getOrElse(return CFuture.completedFuture(symbolInfo_list)) @@ -126,7 +128,7 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonNotification("textDocument/didClose") def onDidCloseDocument(params: DidCloseTextDocumentParams): Unit = { - println("On closing document") + Log.info("On closing document") try { val uri = params.getTextDocument.getUri Common.isViperSourceFile(uri).thenAccept(isViperFile => { @@ -139,14 +141,14 @@ abstract class StandardReceiver extends LanguageClientAware { @JsonRequest(value = "shutdown") def onShutdown(): CFuture[AnyRef] = { - println("shutdown") + Log.debug("shutdown") received_shutdown = true CFuture.completedFuture(null) } @JsonNotification(value = "exit") def onExit(): Unit = { - println("exit") + Log.debug("exit") Coordinator.verifier.stop() if(received_shutdown) sys.exit(0) else sys.exit(1) } @@ -156,7 +158,7 @@ class CustomReceiver extends StandardReceiver { @JsonNotification(S2C_Commands.FileClosed) def onFileClosed(uri: String): Unit = { - println("On closing file") + Log.debug("On closing file") val manager_opt = Coordinator.files.get(uri) val manager = manager_opt.getOrElse(return) manager.resetDiagnostics() @@ -165,6 +167,7 @@ class CustomReceiver extends StandardReceiver { @JsonRequest(C2S_Commands.RemoveDiagnostics) def onRemoveDiagnostics(uri: String): CFuture[Boolean] = { + Log.debug("On removing diagnostics") val manager_opt = Coordinator.files.get(uri) val manager = manager_opt.getOrElse(return CFuture.completedFuture(false)) manager.resetDiagnostics() @@ -173,12 +176,13 @@ class CustomReceiver extends StandardReceiver { @JsonRequest("GetLanguageServerUrl") def onGetServerUrl(): CFuture[String] = { + Log.debug("On getting server URL") CFuture.completedFuture(Coordinator.getAddress) } @JsonNotification(C2S_Commands.StartBackend) def onStartBackend(backend: String): Unit = { - println("Starting ViperServeService") + Log.debug("On starting ViperServeService") try { if(backend == "Silicon" || backend == "Carbon") { Coordinator.backend = BackendProperties(name = s"Viper$backend", backend_type = backend) @@ -194,6 +198,7 @@ class CustomReceiver extends StandardReceiver { @JsonNotification(C2S_Commands.SwapBackend) def onSwapBackend(backend: String): Unit = { + Log.debug("on swapping backend") try { if(backend == "Silicon" || backend == "Carbon") { Coordinator.backend = BackendProperties(name = s"Viper$backend", backend_type = backend) @@ -211,21 +216,23 @@ class CustomReceiver extends StandardReceiver { @JsonRequest(C2S_Commands.RequestBackendNames) def onGetNames(backendName: String): CFuture[Array[String]] = { + Log.debug("on getting backend names") CFuture.completedFuture(Array("Silicon", "Carbon")) } @JsonNotification(C2S_Commands.StopBackend) def onBackendStop(backendName: String): Unit= { + Log.debug("on stopping backend") Coordinator.verifier.setStopping() Coordinator.verifier.setStopped() } @JsonNotification(C2S_Commands.Verify) def onVerify(data: VerifyRequest): Unit = { + Log.debug("On verifying") if (Coordinator.canVerificationBeStarted(data.uri, data.manuallyTriggered)) { //stop all other verifications because the backend crashes if multiple verifications are run in parallel Coordinator.stopAllRunningVerifications().thenAccept(_ => { - println("Verifications stopped successfully") Log.log("start or restart verification", LogLevel.Info) val manager = Coordinator.files.getOrElse(data.uri, return) @@ -248,14 +255,14 @@ class CustomReceiver extends StandardReceiver { @JsonNotification(C2S_Commands.FlushCache) def onFlushCache(file: String): Unit = { - println("flushing cache...") + Log.debug("flushing cache...") val file_arg: Option[String] = if(file == null) Option.empty else Some(file) Coordinator.verifier.flushCache(file_arg) } @JsonNotification(C2S_Commands.StopVerification) def onStopVerification(uri: String): CFuture[Boolean] = { - println("on stopping verification") + Log.debug("on stopping verification") try { val manager = Coordinator.files.getOrElse(uri, return CFuture.completedFuture(false)) manager.abortVerification().thenApply((success) => { @@ -271,7 +278,7 @@ class CustomReceiver extends StandardReceiver { } override def connect(client: LanguageClient): Unit = { - println("Connecting plugin client.") + Log.debug("Connecting plugin client.") val c = client.asInstanceOf[IdeLanguageClient] Coordinator.client = c } diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index 3f7f2bf..839358c 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -56,7 +56,7 @@ sealed trait CustomizableHttp extends BasicHttp { * server. In particular, this means providing a protocol that returns the VerificationServer's * responses as type [[ToResponseMarshallable]]. * */ -trait VerificationServerHTTP extends VerificationServer with CustomizableHttp { +trait VerificationServerHttp extends VerificationServer with CustomizableHttp { def setRoutes(): Route diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index e576d1f..28d6327 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -27,11 +27,7 @@ case class JobNotFoundException() extends VerificationServerException case class JobID(id: Int) case class JobHandle(job_actor: ActorRef, queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) { - - object controller_actor - -} + publisher: Publisher[Envelope]) /** This class manages the verification jobs the server receives. */ diff --git a/src/test/scala/ViperServerTests.scala b/src/test/scala/ViperServerTests.scala index 59fb906..c2a50db 100644 --- a/src/test/scala/ViperServerTests.scala +++ b/src/test/scala/ViperServerTests.scala @@ -26,7 +26,7 @@ class ViperServerSpec extends WordSpec with Matchers with ScalatestRouteTest { HttpServerRunner.main(Array()) - private val _routsUnderTest = HttpServerRunner.viperServerHTTP.routes() + private val _routsUnderTest = HttpServerRunner.viperServerHttp.routes() def printRequestResponsePair(req: String, res: String): Unit = { println(s">>> ViperServer test request `$req` response in the following response: $res") From 204ef73ecdbeb55cb5c6a61fc5f99ed30b3566bb Mon Sep 17 00:00:00 2001 From: Valentin Date: Thu, 22 Oct 2020 13:04:40 +0200 Subject: [PATCH 17/79] adjusted runner objects --- .../scala/viper/server/HttpServerRunner.scala | 24 -------- .../scala/viper/server/LanguageServer.scala | 57 +++++++++++++++++++ src/main/scala/viper/server/ViperServer.scala | 49 +++------------- .../frontends/lsp/CommandProtocol.scala | 54 ++++++++++-------- .../server/frontends/lsp/Coordinator.scala | 3 - .../viper/server/frontends/lsp/Receiver.scala | 1 - src/test/scala/ViperServerTests.scala | 5 +- 7 files changed, 98 insertions(+), 95 deletions(-) delete mode 100644 src/main/scala/viper/server/HttpServerRunner.scala create mode 100644 src/main/scala/viper/server/LanguageServer.scala diff --git a/src/main/scala/viper/server/HttpServerRunner.scala b/src/main/scala/viper/server/HttpServerRunner.scala deleted file mode 100644 index 1f71aaa..0000000 --- a/src/main/scala/viper/server/HttpServerRunner.scala +++ /dev/null @@ -1,24 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ - -import viper.server.frontends.http.ViperHttpServer - -object HttpServerRunner { - var viperServerHttp: ViperHttpServer = _ - - /** Start VCS in HTTP mode. - * */ - def startHttpServer(args: Array[String]): Unit = { - viperServerHttp = new ViperHttpServer(args) - viperServerHttp.start() - } - - def main(args: Array[String]): Unit = { - startHttpServer(args) - } -} \ No newline at end of file diff --git a/src/main/scala/viper/server/LanguageServer.scala b/src/main/scala/viper/server/LanguageServer.scala new file mode 100644 index 0000000..4c9b80b --- /dev/null +++ b/src/main/scala/viper/server/LanguageServer.scala @@ -0,0 +1,57 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2011-2020 ETH Zurich. + */ + + +import java.io.IOException +import java.net.Socket + +import org.eclipse.lsp4j.jsonrpc.Launcher +import viper.server.frontends.lsp.{Coordinator, CustomReceiver, IdeLanguageClient} + +object LanguageServerRunner { + + def main(args: Array[String]): Unit = { + try { + val port = Integer.parseInt(args.head) + runServer(port) + } catch { + case _: NoSuchElementException => { + println("No port number provided") + sys.exit(1) + } + case _: NumberFormatException => { + println("Invalid port number") + sys.exit(1) + } + } + } + + def runServer(port: Int): Unit = { + // start listening on port + try { + val socket = new Socket("localhost", port) + val localAddress = socket.getLocalAddress.getHostAddress + println(s"going to listen on $localAddress:$port") + + Coordinator.port = port + Coordinator.url = localAddress + + val server: CustomReceiver = new CustomReceiver() + val launcher = Launcher.createLauncher(server, classOf[IdeLanguageClient], socket.getInputStream, socket.getOutputStream) + server.connect(launcher.getRemoteProxy) + + // start listening on input stream in a new thread: + val fut = launcher.startListening() + // wait until stream is closed again + fut.get() + } catch { + case e: IOException => println(s"IOException occurred: ${e.toString}") + } + } +} + diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServer.scala index 9e02147..b54bd7b 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServer.scala @@ -6,50 +6,19 @@ package viper.server -import java.io.IOException -import java.net.Socket - -import org.eclipse.lsp4j.jsonrpc.Launcher -import viper.server.frontends.lsp.{Coordinator, CustomReceiver, IdeLanguageClient} +import viper.server.frontends.http.ViperHttpServer object ViperServerRunner { + var viperServerHttp: ViperHttpServer = _ - def main(args: Array[String]): Unit = { - try { - val port = Integer.parseInt(args.head) - runServer(port) - } catch { - case _: NoSuchElementException => { - println("No port number provided") - sys.exit(1) - } - case _: NumberFormatException => { - println("Invalid port number") - sys.exit(1) - } - } + /** Start VCS in HTTP mode. + * */ + def startHttpServer(args: Array[String]): Unit = { + viperServerHttp = new ViperHttpServer(args) + viperServerHttp.start() } - def runServer(port: Int): Unit = { - // start listening on port - try { - val socket = new Socket("localhost", port) - val localAddress = socket.getLocalAddress.getHostAddress - println(s"going to listen on $localAddress:$port") - - Coordinator.port = port - Coordinator.url = localAddress - - val server: CustomReceiver = new CustomReceiver() - val launcher = Launcher.createLauncher(server, classOf[IdeLanguageClient], socket.getInputStream, socket.getOutputStream) - server.connect(launcher.getRemoteProxy) - - // start listening on input stream in a new thread: - val fut = launcher.startListening() - // wait until stream is closed again - fut.get() - } catch { - case e: IOException => println(s"IOException occurred: ${e.toString}") - } + def main(args: Array[String]): Unit = { + startHttpServer(args) } } diff --git a/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala index a1a5a89..63d4fa3 100644 --- a/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala @@ -8,16 +8,22 @@ package viper.server.frontends.lsp +/** This file contains custom LSP commands. + * + * There exists a similar file in the Viper IDE client, called 'ViperProtocol.ts', that should + * contain the same set of commands. The set of commands in both files should be kept in sync. + * */ + object C2S_Commands { - final val RequestBackendNames = "RequestBackendNames" //void - final val Dispose = "Dispose" //void - final val Verify = "Verify" //VerifyParams - final val StopVerification = "StopVerification"//filePath:String - final val ShowHeap = "ShowHeap"//ShowHeapParams - final val StartBackend = "StartBackend"//backendName:String - final val StopBackend = "StopBackend"//void - final val SwapBackend = "SwapBackend"//backendName:String - final val GetExecutionTrace = "GetExecutionTrace"//GetExecutionTraceParams -> trace:ExecutionTrace[] + final val RequestBackendNames = "RequestBackendNames" + final val Dispose = "Dispose" + final val Verify = "Verify" + final val StopVerification = "StopVerification" + final val ShowHeap = "ShowHeap" + final val StartBackend = "StartBackend" + final val StopBackend = "StopBackend" + final val SwapBackend = "SwapBackend" + final val GetExecutionTrace = "GetExecutionTrace" final val RemoveDiagnostics = "RemoveDiagnostics" final val UpdateViperTools = "UpdateViperTools" final val GetViperFileEndings = "GetViperFileEndings" @@ -30,21 +36,21 @@ object C2S_Commands { object S2C_Commands { final val BackendChange = "BackendChange" final val CheckIfSettingsVersionsSpecified = "CheckIfSettingsVersionsSpecified" - final val SettingsChecked = "SettingsChecked" //SettingsCheckedParams - final val RequestRequiredVersion = "RequestRequiredVersion" //void -> requiredVersions: Versions - final val StateChange = "StateChange" //StateChangeParams - final val Log = "Log" //LogParams - final val Error = "Error" //LogParams - final val ToLogFile = "ToLogFile" //LogParams - final val Hint = "Hint" //message: String - final val Progress = "Progress" //message: {domain:String, curr:number, total:number} - final val FileOpened = "FileOpened" //uri: String - final val FileClosed = "FileClosed" //uri: String - final val VerificationNotStarted = "VerificationNotStarted" //uri: String - final val StopDebugging = "StopDebugging" //void - final val BackendReady = "BackendReady" //BackendReadyParams - final val StepsAsDecorationOptions = "StepsAsDecorationOptions" //StepsAsDecorationOptionsResult - final val HeapGraph = "HeapGraph" //HeapGraph + final val SettingsChecked = "SettingsChecked" + final val RequestRequiredVersion = "RequestRequiredVersion" + final val StateChange = "StateChange" + final val Log = "Log" + final val Error = "Error" + final val ToLogFile = "ToLogFile" + final val Hint = "Hint" + final val Progress = "Progress" + final val FileOpened = "FileOpened" + final val FileClosed = "FileClosed" + final val VerificationNotStarted = "VerificationNotStarted" + final val StopDebugging = "StopDebugging" + final val BackendReady = "BackendReady" + final val StepsAsDecorationOptions = "StepsAsDecorationOptions" + final val HeapGraph = "HeapGraph" final val UnhandledViperServerMessageType = "UnhandledViperServerMessageType" final val BackendStarted = "BackendStarted" } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala index a83f65e..d7eeea8 100644 --- a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala +++ b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala @@ -10,10 +10,7 @@ package viper.server.frontends.lsp import java.util.concurrent.CompletableFuture -import org.eclipse.lsp4j.TextDocumentItem - import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer object Coordinator { var port: Int = _ diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index 397c873..fe7b7b7 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -278,7 +278,6 @@ class CustomReceiver extends StandardReceiver { } override def connect(client: LanguageClient): Unit = { - Log.debug("Connecting plugin client.") val c = client.asInstanceOf[IdeLanguageClient] Coordinator.client = c } diff --git a/src/test/scala/ViperServerTests.scala b/src/test/scala/ViperServerTests.scala index c2a50db..83f4801 100644 --- a/src/test/scala/ViperServerTests.scala +++ b/src/test/scala/ViperServerTests.scala @@ -12,7 +12,6 @@ import akka.http.scaladsl.model.{StatusCodes, _} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestDuration import org.scalatest.{Matchers, WordSpec} - import viper.server.ViperServerRunner import viper.server.vsi.Requests._ @@ -24,9 +23,9 @@ class ViperServerSpec extends WordSpec with Matchers with ScalatestRouteTest { implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() implicit val requestTimeput: RouteTestTimeout = RouteTestTimeout(10.second dilated) - HttpServerRunner.main(Array()) + ViperServerRunner.main(Array()) - private val _routsUnderTest = HttpServerRunner.viperServerHttp.routes() + private val _routsUnderTest = ViperServerRunner.viperServerHttp.routes() def printRequestResponsePair(req: String, res: String): Unit = { println(s">>> ViperServer test request `$req` response in the following response: $res") From aa83feb37e8a9b082ac2a54fbf6b85bd62ce99f6 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 11:02:30 +0100 Subject: [PATCH 18/79] Resolved compilation errors after merge. --- src/main/scala/viper/server/frontends/lsp/Common.scala | 4 ++-- src/main/scala/viper/server/frontends/lsp/Receiver.scala | 2 +- .../scala/viper/server/frontends/lsp/ViperServerService.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/viper/server/frontends/lsp/Common.scala b/src/main/scala/viper/server/frontends/lsp/Common.scala index 842baa6..8ccf846 100644 --- a/src/main/scala/viper/server/frontends/lsp/Common.scala +++ b/src/main/scala/viper/server/frontends/lsp/Common.scala @@ -12,7 +12,7 @@ import java.net.URI import java.nio.file.{Path, Paths} import java.util.concurrent.CompletableFuture -import org.eclipse.lsp4j.{Position, Range} +import org.eclipse.lsp4j.Position object Common { @@ -23,7 +23,7 @@ object Common { } def uriToPath(uri: URI): Path = { - Path.of(uri) + Paths.get(uri) } def filenameFromUri(uri: String): String = { diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index fe7b7b7..27eeb12 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -122,7 +122,7 @@ abstract class StandardReceiver extends LanguageClientAware { // No definition found - maybe it's a keyword. val e = s"Verification task not found for URI ${document.getUri}" Log.debug(e) - CFuture.failedFuture(new Throwable(e)) // needs to return some CF. + CFuture.completedFuture({throw new Throwable(e)}) // needs to return some CF. } } diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index c1d5437..9722b51 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -131,7 +131,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with }) case _ => // Did not find a job with this jid. - CFuture.failedFuture(new Throwable(s"The verification job #$jid does not exist.")) + CFuture.completedFuture({throw new Throwable(s"The verification job #$jid does not exist.")}) } } From e221128eff38d6c24049cbc0a661d2c5791a4303 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 11:17:38 +0100 Subject: [PATCH 19/79] Factored out some classes from VerificationServer --- src/main/scala/viper/server/vsi/HTTP.scala | 2 +- .../scala/viper/server/vsi/JobActor.scala | 54 +++++ src/main/scala/viper/server/vsi/JobPool.scala | 45 ++++ .../scala/viper/server/vsi/QueueActor.scala | 24 ++ .../scala/viper/server/vsi/Terminator.scala | 48 ++++ .../viper/server/vsi/VerificationServer.scala | 212 +----------------- .../viper/server/vsi/VerificationTask.scala | 59 +++++ 7 files changed, 239 insertions(+), 205 deletions(-) create mode 100644 src/main/scala/viper/server/vsi/JobActor.scala create mode 100644 src/main/scala/viper/server/vsi/JobPool.scala create mode 100644 src/main/scala/viper/server/vsi/QueueActor.scala create mode 100644 src/main/scala/viper/server/vsi/Terminator.scala create mode 100644 src/main/scala/viper/server/vsi/VerificationTask.scala diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index 839358c..c678922 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -65,7 +65,7 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { override def start(active_jobs: Int): Unit = { jobs = new JobPool(active_jobs) bindingFuture = Http().bindAndHandle(setRoutes, "localhost", port) - _termActor = system.actorOf(Terminator.props(bindingFuture), "terminator") + _termActor = system.actorOf(Terminator.props(jobs, Some(bindingFuture)), "terminator") isRunning = true } diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala new file mode 100644 index 0000000..eca8459 --- /dev/null +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -0,0 +1,54 @@ +package viper.server.vsi + +import akka.actor.{Actor, Props} +import akka.stream.scaladsl.SourceQueueWithComplete +import org.reactivestreams.Publisher + +// --- Actor: JobActor --- + +object JobActor { + def props(id: Int): Props = Props(new JobActor(id)) +} + +class JobActor(private val id: Int) extends Actor { + + private var _verificationTask: Thread = _ + + private def interrupt: Boolean = { + if (_verificationTask != null && _verificationTask.isAlive) { + _verificationTask.interrupt() + _verificationTask.join() + return true + } + false + } + + private def resetVerificationTask(): Unit = { + if (_verificationTask != null && _verificationTask.isAlive) { + _verificationTask.interrupt() + _verificationTask.join() + } + _verificationTask = null + } + + override def receive: PartialFunction[Any, Unit] = { + case VerificationProtocol.Stop => + val did_I_interrupt = interrupt + if (did_I_interrupt) { + sender ! s"Job #$id has been successfully interrupted." + } else { + sender ! s"Job #$id has already been finalized." + } + case VerificationProtocol.Verify(task, queue, publisher) => + resetVerificationTask() + sender ! startJob(task, queue, publisher) + case msg => + throw new Exception("Main Actor: unexpected message received: " + msg) + } + + private def startJob(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]): JobHandle = { + _verificationTask = task + _verificationTask.start() + JobHandle(self, queue, publisher) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala new file mode 100644 index 0000000..683b9b1 --- /dev/null +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -0,0 +1,45 @@ +package viper.server.vsi + +import akka.actor.ActorRef +import akka.stream.scaladsl.SourceQueueWithComplete +import org.reactivestreams.Publisher + +import scala.collection.mutable +import scala.concurrent.Future + +case class JobID(id: Int) +case class JobHandle(job_actor: ActorRef, + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope]) + +/** This class manages the verification jobs the server receives. + */ +class JobPool(val MAX_ACTIVE_JOBS: Int = 3) { + var jobHandles: mutable.Map[Int, Future[JobHandle]] = mutable.Map[Int, Future[JobHandle]]() + private var _nextJobId: Int = 0 + + def newJobsAllowed = jobHandles.size < MAX_ACTIVE_JOBS + + /** Creates a Future of a JobHandle. + * + * For the next available job ID the function job_executor will set up a JobActor. That actor + * will start a verification process and produce a Future JobHandle. The Future will + * successfully complete as soon as the verification process was started successfully. + * */ + def bookNewJob(job_executor: Int => Future[JobHandle]): (Int, Future[JobHandle]) = { + val new_jid = _nextJobId + jobHandles(new_jid) = job_executor(new_jid) + _nextJobId = _nextJobId + 1 + (new_jid, jobHandles(new_jid)) + } + + /** Discards the JobHandle for the given JobID + * */ + def discardJob(jid: JobID): mutable.Map[Int, Future[JobHandle]] = { + jobHandles -= jid.id + } + + def lookupJob(jid: JobID): Option[ Future[JobHandle] ] = { + jobHandles.get(jid.id) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/QueueActor.scala b/src/main/scala/viper/server/vsi/QueueActor.scala new file mode 100644 index 0000000..bb7e08a --- /dev/null +++ b/src/main/scala/viper/server/vsi/QueueActor.scala @@ -0,0 +1,24 @@ +package viper.server.vsi + +import akka.actor.{Actor, PoisonPill, Props} +import akka.stream.scaladsl.SourceQueueWithComplete + +// --- Actor: MessageActor --- + +object QueueActor { + def props(jid: Int, queue: SourceQueueWithComplete[Envelope]): Props = + Props(new QueueActor(jid, queue)) +} + +class QueueActor(jid: Int, queue: SourceQueueWithComplete[Envelope]) extends Actor { + + override def receive: PartialFunction[Any, Unit] = { + case TaskProtocol.BackendReport(msg) => + val offer_status = queue.offer(msg) + sender() ! offer_status + case TaskProtocol.FinalBackendReport(_) => + queue.complete() + self ! PoisonPill + case _ => + } +} diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala new file mode 100644 index 0000000..577b6a2 --- /dev/null +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -0,0 +1,48 @@ +package viper.server.vsi + +import akka.Done +import akka.actor.{Actor, ActorSystem, Props} +import akka.http.scaladsl.Http + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + +// --- Actor: Terminator --- + + +object Terminator { + case object Exit + case class WatchJobQueue(jid: JobID, handle: JobHandle) + + def props(jobs: JobPool, + bindingFuture: Option[Future[Http.ServerBinding]] = None) + (implicit ctx: ExecutionContext, sys: ActorSystem): Props = Props(new Terminator(jobs, bindingFuture)(ctx, sys)) +} + +class Terminator(jobs: JobPool, + bindingFuture: Option[Future[Http.ServerBinding]]) + (implicit val ctx: ExecutionContext, + implicit val sys: ActorSystem) extends Actor { + + override def receive: PartialFunction[Any, Unit] = { + case Terminator.Exit => + bindingFuture match { + case Some(future) => + future + .flatMap(_.unbind()) // trigger unbinding from the port + .onComplete(_ => { + sys.terminate() // and shutdown when done + }) + case None => + sys.terminate() // shutdown + } + case Terminator.WatchJobQueue(jid, handle) => + val queue_completion_future: Future[Done] = handle.queue.watchCompletion() + queue_completion_future.onComplete( { + case Failure(e) => + throw e + case Success(_) => + jobs.discardJob(jid) + }) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 28d6327..ed9fcc6 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -6,61 +6,21 @@ package viper.server.vsi -import akka.actor.{Actor, ActorRef, ActorSystem, PoisonPill, Props} -import akka.http.scaladsl.Http +import akka.NotUsed +import akka.actor.{ActorRef, ActorSystem} import akka.pattern.ask -import akka.stream.scaladsl.{Keep, Sink, Source, SourceQueueWithComplete} -import akka.stream.{ActorMaterializer, OverflowStrategy, QueueOfferResult} +import akka.stream.scaladsl.{Keep, Sink, Source} +import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.util.Timeout -import akka.{Done, NotUsed} -import org.reactivestreams.Publisher -import scala.collection.mutable import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} import scala.util.{Failure, Success} class VerificationServerException extends Exception case class JobNotFoundException() extends VerificationServerException -case class JobID(id: Int) -case class JobHandle(job_actor: ActorRef, - queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) - -/** This class manages the verification jobs the server receives. - */ -class JobPool(val MAX_ACTIVE_JOBS: Int = 3) { - var jobHandles: mutable.Map[Int, Future[JobHandle]] = mutable.Map[Int, Future[JobHandle]]() - private var _nextJobId: Int = 0 - - def newJobsAllowed = jobHandles.size < MAX_ACTIVE_JOBS - - /** Creates a Future of a JobHandle. - * - * For the next available job ID the function job_executor will set up a JobActor. That actor - * will start a verification process and produce a Future JobHandle. The Future will - * successfully complete as soon as the verification process was started successfully. - * */ - def bookNewJob(job_executor: Int => Future[JobHandle]): (Int, Future[JobHandle]) = { - val new_jid = _nextJobId - jobHandles(new_jid) = job_executor(new_jid) - _nextJobId = _nextJobId + 1 - (new_jid, jobHandles(new_jid)) - } - - /** Discards the JobHandle for the given JobID - * */ - def discardJob(jid: JobID): mutable.Map[Int, Future[JobHandle]] = { - jobHandles -= jid.id - } - - def lookupJob(jid: JobID): Option[ Future[JobHandle] ] = { - jobHandles.get(jid.id) - } -} - /** This trait provides state and process management functionality for verification servers. * * The server runs on Akka's actor system. This means that the entire server's state @@ -79,8 +39,9 @@ class JobPool(val MAX_ACTIVE_JOBS: Int = 3) { trait VerificationServer extends Unpacker { implicit val system: ActorSystem = ActorSystem("Main") - implicit val executionContext = ExecutionContext.global + implicit val executionContext: ExecutionContextExecutor = ExecutionContext.global implicit val materializer: ActorMaterializer = ActorMaterializer() + protected var _termActor: ActorRef = _ protected var jobs: JobPool = _ var isRunning: Boolean = false @@ -92,118 +53,10 @@ trait VerificationServer extends Unpacker { * */ def start(active_jobs: Int): Unit = { jobs = new JobPool(active_jobs) - _termActor = system.actorOf(Terminator.props(), "terminator") + _termActor = system.actorOf(Terminator.props(jobs), "terminator") isRunning = true } - // --- Actor: Terminator --- - - protected var _termActor: ActorRef = _ - - object Terminator { - case object Exit - case class WatchJobQueue(jid: JobID, handle: JobHandle) - - def props(bindingFuture: Future[Http.ServerBinding]): Props = Props(new Terminator(Some(bindingFuture))) - def props(): Props = Props(new Terminator(None)) - } - - class Terminator(bindingFuture: Option[Future[Http.ServerBinding]]) extends Actor { - - override def receive: PartialFunction[Any, Unit] = { - case Terminator.Exit => - bindingFuture match { - case Some(future) => - future - .flatMap(_.unbind()) // trigger unbinding from the port - .onComplete(_ => { - system.terminate() // and shutdown when done - }) - case None => - system.terminate() // shutdown - } - case Terminator.WatchJobQueue(jid, handle) => - val queue_completion_future: Future[Done] = handle.queue.watchCompletion() - queue_completion_future.onComplete( { - case Failure(e) => - throw e - case Success(_) => - jobs.discardJob(jid) - }) - } - } - - - // --- Actor: JobActor --- - - object JobActor { - def props(id: Int): Props = Props(new JobActor(id)) - } - - class JobActor(private val id: Int) extends Actor { - - private var _verificationTask: Thread = _ - - private def interrupt: Boolean = { - if (_verificationTask != null && _verificationTask.isAlive) { - _verificationTask.interrupt() - _verificationTask.join() - return true - } - false - } - - private def resetVerificationTask(): Unit = { - if (_verificationTask != null && _verificationTask.isAlive) { - _verificationTask.interrupt() - _verificationTask.join() - } - _verificationTask = null - } - - override def receive: PartialFunction[Any, Unit] = { - case VerificationProtocol.Stop => - val did_I_interrupt = interrupt - if (did_I_interrupt) { - sender ! s"Job #$id has been successfully interrupted." - } else { - sender ! s"Job #$id has already been finalized." - } - case VerificationProtocol.Verify(task, queue, publisher) => - resetVerificationTask() - sender ! startJob(task, queue, publisher) - case msg => - throw new Exception("Main Actor: unexpected message received: " + msg) - } - - private def startJob(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]): JobHandle = { - _verificationTask = task - _verificationTask.start() - JobHandle(self, queue, publisher) - } - } - - - // --- Actor: MessageActor --- - - object QueueActor { - def props(jid: Int, queue: SourceQueueWithComplete[Envelope]): Props = - Props(new QueueActor(jid, queue)) - } - - class QueueActor(jid: Int, queue: SourceQueueWithComplete[Envelope]) extends Actor { - - override def receive: PartialFunction[Any, Unit] = { - case TaskProtocol.BackendReport(msg) => - val offer_status = queue.offer(msg) - sender() ! offer_status - case TaskProtocol.FinalBackendReport(_) => - queue.complete() - self ! PoisonPill - case _ => - } - } - /** This method starts a verification process. * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. @@ -293,52 +146,3 @@ trait VerificationServer extends Unpacker { overall_interrupt_future } } - - -/** This class is a generic wrapper for a any sort of verification a VerificationServer might - * work on. - * - * It has the following properties: - * - implements runnable - * - provides a reference to a queue actor. - * - * The first serves the purpose of running the process concurrently. The second allows to - * communicate from the verification process to the server. - * */ -abstract class VerificationTask()(implicit val executionContext: ExecutionContext) extends Runnable with Packer { - - private var q_actor: ActorRef = _ - - final def setQueueActor(actor: ActorRef): Unit = { - q_actor = actor - } - - /** Sends massage to the attached actor. - * - * The actor receiving this message offers it to a queue. This offering returns a Future, - * which will eventually indicate whether or not the offer was successful. This method is - * blocking, as it waits for the successful completion of such an offer. - * */ - protected def enqueueMessages(msg: A): Unit = { - implicit val askTimeout: Timeout = Timeout(5000 milliseconds) - - var current_offer: Future[QueueOfferResult] = null - val answer = q_actor ? TaskProtocol.BackendReport(pack(msg)) - current_offer = answer.flatMap({ - case res: Future[QueueOfferResult] => res - }) - while(current_offer == null || !current_offer.isCompleted){ - Thread.sleep(10) - } - } - - /** Notify the queue actor that the task has come to an end - * - * The actor receiving this message will close the queue. - * - * @param success indicates whether or not the task has ended as successfully. - * */ - protected def registerTaskEnd(success: Boolean): Unit = { - q_actor ! TaskProtocol.FinalBackendReport(success) - } -} diff --git a/src/main/scala/viper/server/vsi/VerificationTask.scala b/src/main/scala/viper/server/vsi/VerificationTask.scala new file mode 100644 index 0000000..7f14543 --- /dev/null +++ b/src/main/scala/viper/server/vsi/VerificationTask.scala @@ -0,0 +1,59 @@ +package viper.server.vsi + +import akka.actor.ActorRef +import akka.pattern.ask +import akka.stream.QueueOfferResult +import akka.util.Timeout + +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration._ + + + +/** This class is a generic wrapper for a any sort of verification a VerificationServer might + * work on. + * + * It has the following properties: + * - implements runnable + * - provides a reference to a queue actor. + * + * The first serves the purpose of running the process concurrently. The second allows to + * communicate from the verification process to the server. + * */ +abstract class VerificationTask()(implicit val executionContext: ExecutionContext) extends Runnable with Packer { + + private var q_actor: ActorRef = _ + + final def setQueueActor(actor: ActorRef): Unit = { + q_actor = actor + } + + /** Sends massage to the attached actor. + * + * The actor receiving this message offers it to a queue. This offering returns a Future, + * which will eventually indicate whether or not the offer was successful. This method is + * blocking, as it waits for the successful completion of such an offer. + * */ + protected def enqueueMessages(msg: A): Unit = { + implicit val askTimeout: Timeout = Timeout(5000 milliseconds) + + var current_offer: Future[QueueOfferResult] = null + val answer = q_actor ? TaskProtocol.BackendReport(pack(msg)) + current_offer = answer.flatMap({ + case res: Future[QueueOfferResult] => res + }) + while(current_offer == null || !current_offer.isCompleted){ + Thread.sleep(10) + } + } + + /** Notify the queue actor that the task has come to an end + * + * The actor receiving this message will close the queue. + * + * @param success indicates whether or not the task has ended as successfully. + * */ + protected def registerTaskEnd(success: Boolean): Unit = { + q_actor ! TaskProtocol.FinalBackendReport(success) + } +} From 78173e1c4e311790e25980798424429f817e4876 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 11:59:06 +0100 Subject: [PATCH 20/79] Use ViperConfig in LanguageServerRunner --- ...erver.scala => LanguageServerRunner.scala} | 21 ++++---- src/main/scala/viper/server/ViperConfig.scala | 6 ++- ...erServer.scala => ViperServerRunner.scala} | 48 +++++++++---------- 3 files changed, 37 insertions(+), 38 deletions(-) rename src/main/scala/viper/server/{LanguageServer.scala => LanguageServerRunner.scala} (80%) rename src/main/scala/viper/server/{ViperServer.scala => ViperServerRunner.scala} (96%) diff --git a/src/main/scala/viper/server/LanguageServer.scala b/src/main/scala/viper/server/LanguageServerRunner.scala similarity index 80% rename from src/main/scala/viper/server/LanguageServer.scala rename to src/main/scala/viper/server/LanguageServerRunner.scala index 4c9b80b..08120bd 100644 --- a/src/main/scala/viper/server/LanguageServer.scala +++ b/src/main/scala/viper/server/LanguageServerRunner.scala @@ -11,24 +11,19 @@ import java.io.IOException import java.net.Socket import org.eclipse.lsp4j.jsonrpc.Launcher +import viper.server.ViperConfig import viper.server.frontends.lsp.{Coordinator, CustomReceiver, IdeLanguageClient} + object LanguageServerRunner { + private var _config: ViperConfig = _ + def main(args: Array[String]): Unit = { - try { - val port = Integer.parseInt(args.head) - runServer(port) - } catch { - case _: NoSuchElementException => { - println("No port number provided") - sys.exit(1) - } - case _: NumberFormatException => { - println("Invalid port number") - sys.exit(1) - } - } + _config = new ViperConfig(args) + _config.verify() + val port = _config.port() + runServer(port) } def runServer(port: Int): Unit = { diff --git a/src/main/scala/viper/server/ViperConfig.scala b/src/main/scala/viper/server/ViperConfig.scala index 676b6e5..0eeef59 100644 --- a/src/main/scala/viper/server/ViperConfig.scala +++ b/src/main/scala/viper/server/ViperConfig.scala @@ -68,7 +68,11 @@ class ViperConfig(args: Seq[String]) extends ScallopConf(args) { descr = ("Specifies the port on which ViperServer will be started." + s"The port must be an integer in range [${Socket.MIN_PORT_NUMBER}-${ibm.Socket.MAX_PORT_NUMBER}]" + "If the option is omitted, an available port will be selected automatically."), - default = Some(ibm.Socket.findFreePort), + default = { + val p = ibm.Socket.findFreePort + println(s"Automatically selecting port $p ...") + Some(p) + }, validate = p => try { ibm.Socket.available(p) } catch { diff --git a/src/main/scala/viper/server/ViperServer.scala b/src/main/scala/viper/server/ViperServerRunner.scala similarity index 96% rename from src/main/scala/viper/server/ViperServer.scala rename to src/main/scala/viper/server/ViperServerRunner.scala index b54bd7b..9a284e5 100644 --- a/src/main/scala/viper/server/ViperServer.scala +++ b/src/main/scala/viper/server/ViperServerRunner.scala @@ -1,24 +1,24 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2011-2020 ETH Zurich. - -package viper.server - -import viper.server.frontends.http.ViperHttpServer - -object ViperServerRunner { - var viperServerHttp: ViperHttpServer = _ - - /** Start VCS in HTTP mode. - * */ - def startHttpServer(args: Array[String]): Unit = { - viperServerHttp = new ViperHttpServer(args) - viperServerHttp.start() - } - - def main(args: Array[String]): Unit = { - startHttpServer(args) - } -} +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server + +import viper.server.frontends.http.ViperHttpServer + +object ViperServerRunner { + var viperServerHttp: ViperHttpServer = _ + + /** Start VCS in HTTP mode. + * */ + def startHttpServer(args: Array[String]): Unit = { + viperServerHttp = new ViperHttpServer(args) + viperServerHttp.start() + } + + def main(args: Array[String]): Unit = { + startHttpServer(args) + } +} From 72bd15e7bc5aef493bacccaec736d4561fbffc6b Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 13:31:03 +0100 Subject: [PATCH 21/79] Generalized JobPool --- .../viper/server/core/ViperCoreServer.scala | 6 +- .../server/core/ViperCoreServerUtils.scala | 6 +- .../frontends/http/ViperHttpServer.scala | 2 +- .../server/frontends/lsp/FileManager.scala | 4 +- .../frontends/lsp/ViperServerService.scala | 14 ++-- src/main/scala/viper/server/vsi/HTTP.scala | 8 +- .../scala/viper/server/vsi/JobActor.scala | 8 +- src/main/scala/viper/server/vsi/JobPool.scala | 73 ++++++++++++------- .../scala/viper/server/vsi/QueueActor.scala | 4 +- .../scala/viper/server/vsi/Terminator.scala | 6 +- .../viper/server/vsi/VerificationServer.scala | 31 ++++---- src/test/scala/CoreServerTests.scala | 10 +-- 12 files changed, 97 insertions(+), 75 deletions(-) diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 7af69b3..8ef9981 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -9,7 +9,7 @@ package viper.server.core import akka.actor.ActorRef import viper.server.ViperConfig import viper.server.core.ViperBackendConfigs._ -import viper.server.vsi.{Envelope, JobID, VerificationServer} +import viper.server.vsi.{Envelope, VerJobId, VerificationServer} import viper.silver.ast.Program import viper.silver.logger.ViperLogger import viper.silver.reporter.Message @@ -49,7 +49,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer { * * Expects a non-null backend config and Viper AST. * */ - def verify(programID: String, backend_config: ViperBackendConfig, program: Program): JobID = { + def verify(programID: String, backend_config: ViperBackendConfig, program: Program): VerJobId = { require(program != null && backend_config != null) val args: List[String] = backend_config match { @@ -68,7 +68,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer { jid } - override def streamMessages(jid: JobID, clientActor: ActorRef): Option[Future[Unit]] = { + override def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Unit]] = { logger.get.info(s"Streaming results for job #${jid.id}.") super.streamMessages(jid, clientActor) } diff --git a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala index f547c29..4e37f92 100644 --- a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala +++ b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala @@ -9,7 +9,7 @@ package viper.server.core import akka.actor.{Actor, ActorSystem, Props} import akka.pattern.ask import akka.util.Timeout -import viper.server.vsi.{JobID, JobNotFoundException} +import viper.server.vsi.{VerJobId, JobNotFoundException} import viper.silver.reporter.{EntityFailureMessage, Message} import viper.silver.verifier.{AbstractError, VerificationResult, Failure => VerificationFailure, Success => VerificationSuccess} @@ -43,7 +43,7 @@ object ViperCoreServerUtils { * * Deletes the jobHandle on completion. */ - def getMessagesFuture(core: ViperCoreServer, jid: JobID)(implicit actor_system: ActorSystem): Future[List[Message]] = { + def getMessagesFuture(core: ViperCoreServer, jid: VerJobId)(implicit actor_system: ActorSystem): Future[List[Message]] = { import scala.language.postfixOps val actor = actor_system.actorOf(SeqActor.props()) @@ -66,7 +66,7 @@ object ViperCoreServerUtils { * * Deletes the jobHandle on completion. */ - def getResultsFuture(core: ViperCoreServer, jid: JobID)(implicit actor_system: ActorSystem): Future[VerificationResult] = { + def getResultsFuture(core: ViperCoreServer, jid: VerJobId)(implicit actor_system: ActorSystem): Future[VerificationResult] = { val messages_future = getMessagesFuture(core, jid) val result_future: Future[VerificationResult] = messages_future.map(msgs => { diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index fd785d8..436a33c 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -90,7 +90,7 @@ class ViperHttpServer(_args: Array[String]) return VerificationRequestReject("Invalid arguments for backend.") } - val jid: JobID = verify(file, backend, ast) + val jid: VerJobId = verify(file, backend, ast) if (jid.id >= 0) { logger.get.info(s"Verification process #${jid.id} has successfully started.") diff --git a/src/main/scala/viper/server/frontends/lsp/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala index 492f23e..8873801 100644 --- a/src/main/scala/viper/server/frontends/lsp/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -17,7 +17,7 @@ import org.eclipse.lsp4j.{Diagnostic, DiagnosticSeverity, Location, Position, Pu import viper.server.frontends.lsp import viper.server.frontends.lsp.VerificationState._ import viper.server.frontends.lsp.VerificationSuccess._ -import viper.server.vsi.JobID +import viper.server.vsi.VerJobId import viper.silver.ast.{Domain, Field, Function, Method, Predicate, SourcePosition} import viper.silver.reporter._ @@ -89,7 +89,7 @@ class FileManager(file_uri: String) { } Log.info("Aborting running verification.") is_aborting = true - Coordinator.verifier.stopVerification(JobID(jid)).thenAccept(_ => { + Coordinator.verifier.stopVerification(VerJobId(jid)).thenAccept(_ => { is_verifying = false lastSuccess = Aborted }).exceptionally(e => { diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 9722b51..de35a48 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -18,7 +18,7 @@ import viper.server.core.{ViperCache, ViperCoreServer} import viper.server.frontends.lsp.VerificationState.Stopped import viper.server.utility.AstGenerator import viper.server.vsi.VerificationProtocol.Stop -import viper.server.vsi.{JobID, VerificationServer} +import viper.server.vsi.{VerJobId, VerificationServer} import viper.silver.ast.Program import scala.compat.java8.FutureConverters._ @@ -72,7 +72,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with } } - def verify(command: String): JobID = { + def verify(command: String): VerJobId = { Log.debug("Requesting ViperServer to start new job...") val arg_list = getArgListFromArgString(command) @@ -87,11 +87,11 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with } catch { case _: java.nio.file.NoSuchFileException => Log.debug("The file for which verification has been requested was not found.") - return JobID(-1) + return VerJobId(-1) } val ast = ast_option.getOrElse({ Log.debug("The file for which verification has been requested contained syntax errors.") - return JobID(-1) + return VerJobId(-1) }) // prepare backend config @@ -101,7 +101,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with case "custom" :: args => CustomConfig(args) } - val jid: JobID = verify(file, backend, ast) + val jid: VerJobId = verify(file, backend, ast) if (jid.id >= 0) { Log.info(s"Verification process #${jid.id} has successfully started.") } else { @@ -111,13 +111,13 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with jid } - def startStreaming(jid: JobID, relayActor_props: Props): Unit = { + def startStreaming(jid: VerJobId, relayActor_props: Props): Unit = { Log.debug("Sending verification request to ViperServer...") val relay_actor = system.actorOf(relayActor_props) streamMessages(jid, relay_actor) } - def stopVerification(jid: JobID): CFuture[Boolean] = { + def stopVerification(jid: VerJobId): CFuture[Boolean] = { jobs.lookupJob(jid) match { case Some(handle_future) => handle_future.flatMap(handle => { diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index c678922..775ad95 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -63,7 +63,7 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { var bindingFuture: Future[Http.ServerBinding] = _ override def start(active_jobs: Int): Unit = { - jobs = new JobPool(active_jobs) + jobs = new JobPool[VerJobId, VerHandle](active_jobs) bindingFuture = Http().bindAndHandle(setRoutes, "localhost", port) _termActor = system.actorOf(Terminator.props(jobs, Some(bindingFuture)), "terminator") isRunning = true @@ -118,13 +118,13 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { * must be an ID of an existing verification job. */ get { - jobs.lookupJob(JobID(jid)) match { + jobs.lookupJob(VerJobId(jid)) match { case Some(handle_future) => // Found a job with this jid. onComplete(handle_future) { case Success(handle) => val s: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) - _termActor ! Terminator.WatchJobQueue(JobID(jid), handle) + _termActor ! Terminator.WatchJobQueue(VerJobId(jid), handle) complete(unpackMessages(s)) case Failure(error) => complete(verificationRequestRejection(jid, error)) @@ -138,7 +138,7 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { * must be an ID of an existing verification job. */ get { - jobs.lookupJob(JobID(jid)) match { + jobs.lookupJob(VerJobId(jid)) match { case Some(handle_future) => onComplete(handle_future) { case Success(handle) => diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index eca8459..c72dfc7 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -7,10 +7,10 @@ import org.reactivestreams.Publisher // --- Actor: JobActor --- object JobActor { - def props(id: Int): Props = Props(new JobActor(id)) + def props(id: VerJobId): Props = Props(new JobActor(id)) } -class JobActor(private val id: Int) extends Actor { +class JobActor(private val id: VerJobId) extends Actor { private var _verificationTask: Thread = _ @@ -46,9 +46,9 @@ class JobActor(private val id: Int) extends Actor { throw new Exception("Main Actor: unexpected message received: " + msg) } - private def startJob(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]): JobHandle = { + private def startJob(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]): VerHandle = { _verificationTask = task _verificationTask.start() - JobHandle(self, queue, publisher) + VerHandle(self, queue, publisher) } } \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index 683b9b1..c4f8801 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -5,41 +5,62 @@ import akka.stream.scaladsl.SourceQueueWithComplete import org.reactivestreams.Publisher import scala.collection.mutable -import scala.concurrent.Future +import scala.concurrent.{Future, Promise} -case class JobID(id: Int) -case class JobHandle(job_actor: ActorRef, +sealed trait JobId { + val id: Int + def tag: String + override def toString: String = s"${tag}_id_${id}" +} + +case class VerJobId(id: Int) extends JobId { + def tag = "ver" +} + +sealed trait JobHandle { + def tag: String // identify the kind of job this is + val job_actor: ActorRef + val queue: SourceQueueWithComplete[Envelope] + val publisher: Publisher[Envelope] +} + +case class VerHandle(job_actor: ActorRef, queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) + publisher: Publisher[Envelope]) extends JobHandle { + def tag = "VER" +} + +class JobPool[S <: JobId, T <: JobHandle](val MAX_ACTIVE_JOBS: Int = 3) + (implicit val jid_fact: Int => S) { + + private val _jobHandles: mutable.Map[S, Promise[T]] = mutable.Map() + private val _jobExecutors: mutable.Map[S, () => Future[T]] = mutable.Map() + def jobHandles: Map[S, Future[T]] = _jobHandles.map{ case (id, hand) => (id, hand.future) }.toMap -/** This class manages the verification jobs the server receives. - */ -class JobPool(val MAX_ACTIVE_JOBS: Int = 3) { - var jobHandles: mutable.Map[Int, Future[JobHandle]] = mutable.Map[Int, Future[JobHandle]]() private var _nextJobId: Int = 0 - def newJobsAllowed = jobHandles.size < MAX_ACTIVE_JOBS - - /** Creates a Future of a JobHandle. - * - * For the next available job ID the function job_executor will set up a JobActor. That actor - * will start a verification process and produce a Future JobHandle. The Future will - * successfully complete as soon as the verification process was started successfully. - * */ - def bookNewJob(job_executor: Int => Future[JobHandle]): (Int, Future[JobHandle]) = { - val new_jid = _nextJobId - jobHandles(new_jid) = job_executor(new_jid) + def newJobsAllowed: Boolean = jobHandles.size < MAX_ACTIVE_JOBS + + def bookNewJob(job_executor: S => Future[T]): S = { + require(newJobsAllowed) + + val new_jid: S = jid_fact(_nextJobId) + + _jobHandles(new_jid) = Promise() + _jobExecutors(new_jid) = () => job_executor(new_jid) + _nextJobId = _nextJobId + 1 - (new_jid, jobHandles(new_jid)) + new_jid } - /** Discards the JobHandle for the given JobID - * */ - def discardJob(jid: JobID): mutable.Map[Int, Future[JobHandle]] = { - jobHandles -= jid.id + def discardJob(jid: S): Unit = { + _jobHandles -= jid } - def lookupJob(jid: JobID): Option[ Future[JobHandle] ] = { - jobHandles.get(jid.id) + def lookupJob(jid: S): Option[Future[T]] = { + _jobHandles.get(jid).map((promise: Promise[T]) => { + promise.completeWith(_jobExecutors(jid)()) + promise.future + }) } } \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/QueueActor.scala b/src/main/scala/viper/server/vsi/QueueActor.scala index bb7e08a..0aa74f3 100644 --- a/src/main/scala/viper/server/vsi/QueueActor.scala +++ b/src/main/scala/viper/server/vsi/QueueActor.scala @@ -6,11 +6,11 @@ import akka.stream.scaladsl.SourceQueueWithComplete // --- Actor: MessageActor --- object QueueActor { - def props(jid: Int, queue: SourceQueueWithComplete[Envelope]): Props = + def props(jid: VerJobId, queue: SourceQueueWithComplete[Envelope]): Props = Props(new QueueActor(jid, queue)) } -class QueueActor(jid: Int, queue: SourceQueueWithComplete[Envelope]) extends Actor { +class QueueActor(jid: VerJobId, queue: SourceQueueWithComplete[Envelope]) extends Actor { override def receive: PartialFunction[Any, Unit] = { case TaskProtocol.BackendReport(msg) => diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala index 577b6a2..92e9d96 100644 --- a/src/main/scala/viper/server/vsi/Terminator.scala +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -12,14 +12,14 @@ import scala.util.{Failure, Success} object Terminator { case object Exit - case class WatchJobQueue(jid: JobID, handle: JobHandle) + case class WatchJobQueue(jid: VerJobId, handle: VerHandle) - def props(jobs: JobPool, + def props(jobs: JobPool[VerJobId, VerHandle], bindingFuture: Option[Future[Http.ServerBinding]] = None) (implicit ctx: ExecutionContext, sys: ActorSystem): Props = Props(new Terminator(jobs, bindingFuture)(ctx, sys)) } -class Terminator(jobs: JobPool, +class Terminator(jobs: JobPool[VerJobId, VerHandle], bindingFuture: Option[Future[Http.ServerBinding]]) (implicit val ctx: ExecutionContext, implicit val sys: ActorSystem) extends Actor { diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index ed9fcc6..6568bd0 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -43,10 +43,11 @@ trait VerificationServer extends Unpacker { implicit val materializer: ActorMaterializer = ActorMaterializer() protected var _termActor: ActorRef = _ - protected var jobs: JobPool = _ + implicit val jid_fact: Int => VerJobId = VerJobId.apply + protected var jobs: JobPool[VerJobId, VerHandle] = _ var isRunning: Boolean = false - /** Configures an instance of ViperCoreServer. + /** Configures an instance of VerificationServer. * * This function must be called before any other. Calling any other function before this one * will result in an IllegalStateException. @@ -61,13 +62,13 @@ trait VerificationServer extends Unpacker { * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. */ - protected def initializeVerificationProcess(task:VerificationTask): JobID = { + protected def initializeVerificationProcess(task:VerificationTask): VerJobId = { if(!isRunning) { - throw new IllegalStateException("Instance of ViperCoreServer already stopped") + throw new IllegalStateException("Instance of VerificationServer already stopped") } if (jobs.newJobsAllowed) { - def createJob(new_jid: Int): Future[JobHandle] = { + def createJob(new_jid: VerJobId): Future[VerHandle] = { implicit val askTimeout: Timeout = Timeout(5000 milliseconds) val job_actor = system.actorOf(JobActor.props(new_jid), s"job_actor_$new_jid") @@ -78,13 +79,13 @@ trait VerificationServer extends Unpacker { task.setQueueActor(message_actor) val task_with_actor = new Thread(task) val answer = job_actor ? VerificationProtocol.Verify(task_with_actor, queue, publisher) - val new_job_handle: Future[JobHandle] = answer.mapTo[JobHandle] + val new_job_handle: Future[VerHandle] = answer.mapTo[VerHandle] new_job_handle } - val (id, _) = jobs.bookNewJob(createJob) - JobID(id) + val id = jobs.bookNewJob(createJob) + id } else { - JobID(-1) // Process Management running at max capacity. + VerJobId(-1) // Process Management running at max capacity. } } @@ -92,14 +93,14 @@ trait VerificationServer extends Unpacker { * * Deletes the JobHandle on completion. */ - protected def streamMessages(jid: JobID, clientActor: ActorRef): Option[Future[Unit]] = { + protected def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Unit]] = { if(!isRunning) { - throw new IllegalStateException("Instance of ViperCoreServer already stopped") + throw new IllegalStateException("Instance of VerificationServer already stopped") } jobs.lookupJob(jid) match { case Some(handle_future) => - def mapHandle(handle: JobHandle): Future[Unit] = { + def mapHandle(handle: VerHandle): Future[Unit] = { val src_envelope: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) val src_msg: Source[A , NotUsed] = src_envelope.map(e => unpack(e)) src_msg.runWith(Sink.actorRef(clientActor, Success)) @@ -111,14 +112,14 @@ trait VerificationServer extends Unpacker { } } - /** Stops an instance of ViperCoreServer from running. + /** Stops an instance of VerificationServer from running. * * As such it should be the last method called. Calling any other function after stop will * result in an IllegalStateException. * */ def stop(): Unit = { if(!isRunning) { - throw new IllegalStateException("Instance of ViperCoreServer already stopped") + throw new IllegalStateException("Instance of VerificationServer already stopped") } isRunning = false @@ -137,7 +138,7 @@ trait VerificationServer extends Unpacker { protected def getInterruptFutureList(): Future[List[String]] = { val interrupt_future_list: List[Future[String]] = jobs.jobHandles map { case (jid, handle_future) => handle_future.flatMap { - case JobHandle(actor, _, _) => + case VerHandle(actor, _, _) => implicit val askTimeout: Timeout = Timeout(1000 milliseconds) (actor ? VerificationProtocol.Stop).mapTo[String] } diff --git a/src/test/scala/CoreServerTests.scala b/src/test/scala/CoreServerTests.scala index 8f4d38b..2a8c2de 100644 --- a/src/test/scala/CoreServerTests.scala +++ b/src/test/scala/CoreServerTests.scala @@ -72,7 +72,7 @@ class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { core.start() } - var jid: JobID = null + var jid: VerJobId = null "be able to execute 'verify()' without exceptions" in { jid = core.verify(verificationError_file, noCache_backend, verificationError_ast) assert(jid != null) @@ -116,7 +116,7 @@ class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { core.start() } - var jid: JobID = null + var jid: VerJobId = null "be able to execute 'verify()' without exceptions" in { jid = core.verify(sum_file, cache_backend, sum_ast) assert(jid != null) @@ -143,7 +143,7 @@ class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { } "see the future returned by 'getMessagesFuture()' eventually complete unsuccessfully for an inexistent job" in { - val wrong_jid = JobID(42) + val wrong_jid = VerJobId(42) messages_future = ViperCoreServerUtils.getMessagesFuture(core, wrong_jid) while (!messages_future.isCompleted) { Thread.sleep(100) @@ -173,7 +173,7 @@ class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { core.start() val filesAndProgs: List[(String, Program)] = files.zip(programs) - var handlers: List[JobID] = null + var handlers: List[VerJobId] = null "be able to have 'verify()' executed repeatedly without exceptions" in { handlers = filesAndProgs map { case (f, p) => core.verify(f, noCache_backend, p) } } @@ -220,7 +220,7 @@ class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { core.start() val filesAndProgs: List[(String, Program)] = files.zip(programs) - var handlers: List[JobID] = null + var handlers: List[VerJobId] = null "be able to have 'verify()' executed repeatedly without exceptions" in { handlers = filesAndProgs map { case (f, p) => core.verify(f, noCache_backend, p) } } From 2327e1739609ed397932d4df539b71dad621dd4a Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 13:41:29 +0100 Subject: [PATCH 22/79] Renamed VerificationTask to MessageStreamingTask --- .../scala/viper/server/core/VerificationWorker.scala | 9 +++++---- src/main/scala/viper/server/core/ViperCoreServer.scala | 2 ++ ...VerificationTask.scala => MessageStreamingTask.scala} | 4 +++- src/main/scala/viper/server/vsi/VerificationServer.scala | 4 +++- 4 files changed, 13 insertions(+), 6 deletions(-) rename src/main/scala/viper/server/vsi/{VerificationTask.scala => MessageStreamingTask.scala} (92%) diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index 9eee85f..7bb6ab6 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -9,7 +9,7 @@ package viper.server.core import ch.qos.logback.classic.Logger import viper.carbon.CarbonFrontend import viper.server.ViperConfig -import viper.server.vsi.{Envelope, VerificationTask} +import viper.server.vsi.{Envelope, MessageStreamingTask} import viper.silicon.SiliconFrontend import viper.silver.ast._ import viper.silver.frontend.{DefaultStates, SilFrontend} @@ -17,7 +17,7 @@ import viper.silver.reporter.{Reporter, _} import viper.silver.verifier.{AbstractVerificationError, VerificationResult, _} import scala.collection.mutable.ListBuffer -import scala.concurrent.ExecutionContext +import scala.concurrent.{ExecutionContext, Future} import scala.language.postfixOps class ViperServerException extends Exception @@ -33,8 +33,9 @@ case class SilverEnvelope(m: Message) extends Envelope class VerificationWorker(private val viper_config: ViperConfig, private val logger: Logger, private val command: List[String], - private val program: Program)(implicit val ec: ExecutionContext) extends VerificationTask { + private val program: Program)(implicit val ec: ExecutionContext) extends MessageStreamingTask[Program] { + override def artifact: Future[Program] = Future.successful(program) private var backend: ViperBackend = _ private def resolveCustomBackend(clazzName: String, rep: Reporter): Option[SilFrontend] = { @@ -268,7 +269,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program _frontend.verifier.stop() } - private def doCachedVerification(real_program: Program) = { + private def doCachedVerification(real_program: Program): Unit = { /** Top level branch is here for the same reason as in * {{{viper.silver.frontend.DefaultFrontend.verification()}}} */ diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 8ef9981..6c4b084 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -19,6 +19,8 @@ import scala.language.postfixOps class ViperCoreServer(val _args: Array[String]) extends VerificationServer { + override type AST = Program + // --- VCS : Configuration --- protected var _config: ViperConfig = _ final def config: ViperConfig = _config diff --git a/src/main/scala/viper/server/vsi/VerificationTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala similarity index 92% rename from src/main/scala/viper/server/vsi/VerificationTask.scala rename to src/main/scala/viper/server/vsi/MessageStreamingTask.scala index 7f14543..b671e84 100644 --- a/src/main/scala/viper/server/vsi/VerificationTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -20,7 +20,9 @@ import scala.concurrent.duration._ * The first serves the purpose of running the process concurrently. The second allows to * communicate from the verification process to the server. * */ -abstract class VerificationTask()(implicit val executionContext: ExecutionContext) extends Runnable with Packer { +abstract class MessageStreamingTask[T]()(implicit val executionContext: ExecutionContext) extends Runnable with Packer { + + def artifact: Future[T] private var q_actor: ActorRef = _ diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 6568bd0..fbdd375 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -38,6 +38,8 @@ case class JobNotFoundException() extends VerificationServerException */ trait VerificationServer extends Unpacker { + type AST + implicit val system: ActorSystem = ActorSystem("Main") implicit val executionContext: ExecutionContextExecutor = ExecutionContext.global implicit val materializer: ActorMaterializer = ActorMaterializer() @@ -62,7 +64,7 @@ trait VerificationServer extends Unpacker { * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. */ - protected def initializeVerificationProcess(task:VerificationTask): VerJobId = { + protected def initializeVerificationProcess(task: MessageStreamingTask[AST]): VerJobId = { if(!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") } From 4f8c5da210554cf12c57f4a5a91118ac00b13e54 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 15:06:59 +0100 Subject: [PATCH 23/79] Added AstWorker, MessageReportingTask, refactored Packer and Unpacker into ViperPost, added ProgramDefinitionsProvider. --- .../scala/viper/server/core/AstWorker.scala | 59 +++++++++ .../server/core/MessageReportingTask.scala | 25 ++++ .../server/core/VerificationWorker.scala | 19 +-- .../viper/server/core/ViperCoreServer.scala | 12 +- .../scala/viper/server/core/ViperPost.scala | 16 +++ .../frontends/http/ViperHttpServer.scala | 14 +-- .../frontends/lsp/ViperServerService.scala | 13 +- .../viper/server/utility/AstGenerator.scala | 73 +++++------ .../scala/viper/server/utility/Helpers.scala | 12 ++ .../utility/ProgramDefinitionsProvider.scala | 117 ++++++++++++++++++ .../scala/viper/server/vsi/Envelope.scala | 8 +- .../server/vsi/MessageStreamingTask.scala | 6 +- .../viper/server/vsi/VerificationServer.scala | 2 +- src/test/scala/CoreServerTests.scala | 2 +- src/test/scala/ParsingTests.scala | 2 +- 15 files changed, 284 insertions(+), 96 deletions(-) create mode 100644 src/main/scala/viper/server/core/AstWorker.scala create mode 100644 src/main/scala/viper/server/core/MessageReportingTask.scala create mode 100644 src/main/scala/viper/server/core/ViperPost.scala create mode 100644 src/main/scala/viper/server/utility/Helpers.scala create mode 100644 src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala new file mode 100644 index 0000000..f075e82 --- /dev/null +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -0,0 +1,59 @@ +package viper.server.core + + +import ch.qos.logback.classic.Logger +import viper.server.utility.AstGenerator +import viper.server.utility.Helpers.getArgListFromArgString +import viper.silver.ast.Program + + +abstract class AstConstructionException extends Exception +object ViperFileNotFoundException extends AstConstructionException +object InvalidArgumentsException extends AstConstructionException +object AstConstructionFailureException extends AstConstructionException +object OutOfResourcesException extends AstConstructionException + +case class ServerCrashException(e: Throwable) extends Exception(e) + + +import scala.concurrent.{ExecutionContext, Future, Promise} + +class AstWorker(val input: String, + val logger: Logger)(implicit val ec: ExecutionContext) extends MessageReportingTask { + + // private var _ast: Promise[Program] = + private val _artifact_pro: Promise[Program] = Promise() + override def artifact: Future[Program] = _artifact_pro.future + + private def constructAst(): Future[Program] = Future { + + println(">>> AstWorker.constructAst()") + + val arg_list = getArgListFromArgString(input) + val file: String = arg_list.last + + val astGen = new AstGenerator(logger, new ActorReporter("AstGenerationReporter")) + var ast_option: Option[Program] = None + try { + ast_option = astGen.generateViperAst(file) + } catch { + case _: java.nio.file.NoSuchFileException => + println("The file for which verification has been requested was not found.") + throw ViperFileNotFoundException + } + val ast = ast_option match { + case Some(a) => + a + case _ => + println("The file for which verification has been requested contained syntax errors.") + throw AstConstructionFailureException + } + ast + } + + override def run(): Unit = { + println(">>> AstWorker.run()") + _artifact_pro.completeWith(constructAst()) + } + +} diff --git a/src/main/scala/viper/server/core/MessageReportingTask.scala b/src/main/scala/viper/server/core/MessageReportingTask.scala new file mode 100644 index 0000000..f2caa15 --- /dev/null +++ b/src/main/scala/viper/server/core/MessageReportingTask.scala @@ -0,0 +1,25 @@ +package viper.server.core + +import viper.server.vsi.MessageStreamingTask +import viper.silver.ast.Program +import viper.silver.reporter.{Message, Reporter} + +trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost { + +// private val _reporter: + + protected def enqueueMessage(msg: Message): Unit = { + super.enqueueMessage(pack(msg)) + } + + // Implementation of the Reporter interface used by the backend. + class ActorReporter(tag: String) extends Reporter { + val name = s"ViperServer_$tag" + + def report(msg: Message): Unit = { + println(s">>> ActorReporter.report($msg)") + enqueueMessage(msg) + } + } + +} diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index 7bb6ab6..ddbff70 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -9,7 +9,7 @@ package viper.server.core import ch.qos.logback.classic.Logger import viper.carbon.CarbonFrontend import viper.server.ViperConfig -import viper.server.vsi.{Envelope, MessageStreamingTask} +import viper.server.vsi.Envelope import viper.silicon.SiliconFrontend import viper.silver.ast._ import viper.silver.frontend.{DefaultStates, SilFrontend} @@ -28,12 +28,12 @@ case class ViperServerBackendNotFoundException(name: String) extends ViperServer override def toString: String = s"Verification backend (<: SilFrontend) `$name` could not be found." } -case class SilverEnvelope(m: Message) extends Envelope +case class ViperEnvelope(m: Message) extends Envelope class VerificationWorker(private val viper_config: ViperConfig, private val logger: Logger, private val command: List[String], - private val program: Program)(implicit val ec: ExecutionContext) extends MessageStreamingTask[Program] { + private val program: Program)(implicit val ec: ExecutionContext) extends MessageReportingTask { override def artifact: Future[Program] = Future.successful(program) private var backend: ViperBackend = _ @@ -57,15 +57,6 @@ class VerificationWorker(private val viper_config: ViperConfig, } } - // Implementation of the Reporter interface used by the backend. - class ActorReporter(val tag: String) extends Reporter { - val name = s"ViperServer_$tag" - - def report(msg: Message): Unit = { - enqueueMessages(msg) - } - } - def run(): Unit = { try { command match { @@ -89,7 +80,7 @@ class VerificationWorker(private val viper_config: ViperConfig, case _: InterruptedException => case _: java.nio.channels.ClosedByInterruptException => case e: Throwable => - enqueueMessages(ExceptionReport(e)) + enqueueMessage(ExceptionReport(e)) logger.trace(s"Creation/Execution of the verification backend ${if (backend == null) "" else backend.toString} resulted in exception.", e) } finally { try { @@ -111,7 +102,7 @@ class VerificationWorker(private val viper_config: ViperConfig, override type A = Message override def pack(m: A): Envelope = { - SilverEnvelope(m) + ViperEnvelope(m) } } diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 6c4b084..dc74032 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -9,15 +9,14 @@ package viper.server.core import akka.actor.ActorRef import viper.server.ViperConfig import viper.server.core.ViperBackendConfigs._ -import viper.server.vsi.{Envelope, VerJobId, VerificationServer} +import viper.server.vsi.{VerJobId, VerificationServer} import viper.silver.ast.Program import viper.silver.logger.ViperLogger -import viper.silver.reporter.Message import scala.concurrent.Future import scala.language.postfixOps -class ViperCoreServer(val _args: Array[String]) extends VerificationServer { +class ViperCoreServer(val _args: Array[String]) extends VerificationServer with ViperPost { override type AST = Program @@ -88,10 +87,5 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer { super.stop() } - override type A = Message - override def unpack(e: Envelope): A = { - e match { - case SilverEnvelope(m) => m - } - } + } \ No newline at end of file diff --git a/src/main/scala/viper/server/core/ViperPost.scala b/src/main/scala/viper/server/core/ViperPost.scala new file mode 100644 index 0000000..6a828ef --- /dev/null +++ b/src/main/scala/viper/server/core/ViperPost.scala @@ -0,0 +1,16 @@ +package viper.server.core + +import viper.server.vsi.{Envelope, Post} +import viper.silver.reporter.Message + +trait ViperPost extends Post { + override type A = Message + + override def unpack(e: Envelope): Message = { + e match { + case ViperEnvelope(m) => m + } + } + + override def pack(m: Message): Envelope = ViperEnvelope(m) +} diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 436a33c..01788da 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -21,11 +21,12 @@ import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, Silico import viper.server.core.{ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} import viper.server.utility.AstGenerator +import viper.server.utility.Helpers.getArgListFromArgString import viper.server.vsi.Requests.CacheResetRequest import viper.server.vsi._ import viper.silver.ast.Program import viper.silver.logger.ViperLogger -import viper.silver.reporter.Message +import viper.silver.reporter.{Message, Reporter} import scala.util.{Failure, Success, Try} @@ -69,7 +70,7 @@ class ViperHttpServer(_args: Array[String]) val arg_list_partial = arg_list.dropRight(1) // Parse file - val astGen = new AstGenerator(_logger) + val astGen = new AstGenerator(_logger.get) var ast_option: Option[Program] = None try { ast_option = astGen.generateViperAst(file) @@ -210,13 +211,4 @@ class ViperHttpServer(_args: Array[String]) } } } - - private def getArgListFromArgString(arg_str: String): List[String] = { - val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r - val quoted_string = """^["'](.*)["']$""".r - possibly_quoted_string.findAllIn(arg_str).toList.map { - case quoted_string(noqt_a) => noqt_a - case a => a - } - } } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index de35a48..bd29441 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -17,9 +17,11 @@ import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, Silico import viper.server.core.{ViperCache, ViperCoreServer} import viper.server.frontends.lsp.VerificationState.Stopped import viper.server.utility.AstGenerator +import viper.server.utility.Helpers.getArgListFromArgString import viper.server.vsi.VerificationProtocol.Stop import viper.server.vsi.{VerJobId, VerificationServer} import viper.silver.ast.Program +import viper.silver.reporter.{Message, Reporter} import scala.compat.java8.FutureConverters._ import scala.concurrent.Future @@ -63,15 +65,6 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with } } - private def getArgListFromArgString(arg_str: String): List[String] = { - val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r - val quoted_string = """^["'](.*)["']$""".r - possibly_quoted_string.findAllIn(arg_str).toList.map { - case quoted_string(noqt_a) => noqt_a - case a => a - } - } - def verify(command: String): VerJobId = { Log.debug("Requesting ViperServer to start new job...") @@ -80,7 +73,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with val arg_list_partial = arg_list.dropRight(1) // Parse file - val astGen = new AstGenerator(logger) + val astGen = new AstGenerator(_logger.get) var ast_option: Option[Program] = None try { ast_option = astGen.generateViperAst(file) diff --git a/src/main/scala/viper/server/utility/AstGenerator.scala b/src/main/scala/viper/server/utility/AstGenerator.scala index 4be629c..5faf116 100644 --- a/src/main/scala/viper/server/utility/AstGenerator.scala +++ b/src/main/scala/viper/server/utility/AstGenerator.scala @@ -6,23 +6,20 @@ package viper.server.utility -import java.nio.file.Paths - -import viper.silicon.SiliconFrontend +import ch.qos.logback.classic.Logger import viper.silver.ast.Program -import viper.silver.frontend.SilFrontend -import viper.silver.logger.ViperLogger +import viper.silver.frontend.{SilFrontend, ViperAstProvider} import viper.silver.parser.PProgram -import viper.silver.reporter.StdIOReporter +import viper.silver.reporter.{NoopReporter, Reporter} -class AstGenerator (private val _logger: ViperLogger){ - private val ver_backend: SilFrontend = create_backend() +class AstGenerator(private val _logger: Logger, + private val _reporter: Reporter = NoopReporter) extends ProgramDefinitionsProvider { /** Creates a backend that reads and parses the file */ - private def create_backend() : SilFrontend = { - _logger.get.info(s"Creating new verification backend.") - new SiliconFrontend(StdIOReporter("Parsing Reporter", true), _logger.get) + protected override val _frontend: SilFrontend = { + _logger.info(s"Creating new verification backend.") + new ViperAstProvider(_reporter) } /** Parses and translates a Viper file into a Viper AST. @@ -31,46 +28,44 @@ class AstGenerator (private val _logger: ViperLogger){ */ def generateViperAst(vpr_file_path: String): Option[Program] = { val args: Array[String] = Array(vpr_file_path) - _logger.get.info(s"Parsing viper file.") - ver_backend.setVerifier(ver_backend.createVerifier(args.mkString(" "))) - ver_backend.prepare(args) - ver_backend.init(ver_backend.verifier) - ver_backend.reset(Paths.get(ver_backend.config.file())) - val parse_ast = parse() - translate(parse_ast) + _logger.info(s"Parsing viper file.") + _frontend.execute(args) + if (_frontend.errors.isEmpty) { + reportProgramStats() + Some(_frontend.translationResult) + } else { + None + } } /** Parses a Viper file */ private def parse(): Option[PProgram] = { - ver_backend.parsing() - if(ver_backend.errors.isEmpty) { - _logger.get.info("There was no error while parsing!") - Some(ver_backend.parsingResult) + _frontend.parsing() + if(_frontend.errors.isEmpty) { + _logger.info("There was no error while parsing!") + Some(_frontend.parsingResult) } else { - _logger.get.error(s"There was some error while parsing: ${ver_backend.errors}") + _logger.error(s"There was some error while parsing: ${_frontend.errors}") None } } /** Translates a Parsed Viper file into a Viper AST */ - private def translate(parse_ast : Option[PProgram]) : Option[Program] = { - if(parse_ast.isDefined){ - _logger.get.info(s"Translating parsed file.") - ver_backend.semanticAnalysis() - ver_backend.translation() - ver_backend.consistencyCheck() - if(ver_backend.errors.isEmpty){ - _logger.get.info("There was no error while translating!") - ver_backend.verifier.stop() - return Some(ver_backend.translationResult) - } else { - _logger.get.error (s"There was some error while translating ${ver_backend.errors}") - } + private def translate(): Option[Program] = { + _logger.info(s"Translating parsed file.") + _frontend.semanticAnalysis() + _frontend.translation() + _frontend.consistencyCheck() + _frontend.verifier.stop() + + if (_frontend.errors.isEmpty) { + _logger.info("There was no error while translating!") + Some(_frontend.translationResult) + } else { + _logger.error(s"There was some error while translating ${_frontend.errors}") + None } - ver_backend.verifier.stop() - None } } - diff --git a/src/main/scala/viper/server/utility/Helpers.scala b/src/main/scala/viper/server/utility/Helpers.scala new file mode 100644 index 0000000..e02a63a --- /dev/null +++ b/src/main/scala/viper/server/utility/Helpers.scala @@ -0,0 +1,12 @@ +package viper.server.utility + +object Helpers { + def getArgListFromArgString(arg_str: String): List[String] = { + val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r + val quoted_string = """^["'](.*)["']$""".r + possibly_quoted_string.findAllIn(arg_str).toList.map { + case quoted_string(noqt_a) => noqt_a + case a => a + } + } +} diff --git a/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala b/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala new file mode 100644 index 0000000..efa425c --- /dev/null +++ b/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala @@ -0,0 +1,117 @@ +package viper.server.utility + +import viper.silver.ast.{AbstractSourcePosition, Domain, Field, Function, LocalVarDecl, Method, NamedDomainAxiom, Positioned, Predicate, Program, Scope} +import viper.silver.frontend.SilFrontend +import viper.silver.reporter.{Definition, ProgramDefinitionsReport, ProgramOutlineReport, StatisticsReport} + +trait ProgramDefinitionsProvider { + protected val _frontend: SilFrontend + + def collect(program: Program): List[Definition] = { + (program.members.collect { + case t: Method => + (Definition(t.name, "Method", t.pos) +: (t.pos match { + case p: AbstractSourcePosition => + t.formalArgs.map { arg => Definition(arg.name, "Argument", arg.pos, Some(p)) } ++ + t.formalReturns.map { arg => Definition(arg.name, "Return", arg.pos, Some(p)) } + case _ => Seq() + })) ++ t.deepCollectInBody { + case scope: Scope with Positioned => + scope.pos match { + case p: AbstractSourcePosition => + scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } + case _ => Seq() + } + }.flatten + + case t: Function => + (Definition(t.name, "Function", t.pos) +: (t.pos match { + case p: AbstractSourcePosition => + t.formalArgs.map { arg => Definition(arg.name, "Argument", arg.pos, Some(p)) } + case _ => Seq() + })) ++ (t.body match { + case Some(exp) => + exp.deepCollect { + case scope:Scope with Positioned => + scope.pos match { + case p: AbstractSourcePosition => + scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } + case _ => Seq() + } + } flatten + case _ => Seq() + }) + + case t: Predicate => + (Definition(t.name, "Predicate", t.pos) +: (t.pos match { + case p: AbstractSourcePosition => + t.formalArgs.map { arg => Definition(arg.name, "Argument", arg.pos, Some(p)) } + case _ => Seq() + })) ++ (t.body match { + case Some(exp) => + exp.deepCollect { + case scope:Scope with Positioned => + scope.pos match { + case p: AbstractSourcePosition => + scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } + case _ => Seq() + } + } flatten + case _ => Seq() + }) + + case t: Domain => + (Definition(t.name, "Domain", t.pos) +: (t.pos match { + case p: AbstractSourcePosition => + t.functions.flatMap { func => + Definition(func.name, "Function", func.pos, Some(p)) +: (func.pos match { + case func_p: AbstractSourcePosition => + func.formalArgs.map { arg => Definition(if (arg.isInstanceOf[LocalVarDecl]) arg.asInstanceOf[LocalVarDecl].name else "unnamed parameter", "Argument", arg.pos, Some(func_p)) } + case _ => Seq() + }) + } ++ t.axioms.flatMap { ax => + Definition(if (ax.isInstanceOf[NamedDomainAxiom]) ax.asInstanceOf[NamedDomainAxiom].name else "", "Axiom", ax.pos, Some(p)) +: (ax.pos match { + case ax_p: AbstractSourcePosition => + ax.exp.deepCollect { + case scope:Scope with Positioned => + scope.pos match { + case p: AbstractSourcePosition => + scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } + case _ => Seq() + } + } flatten + case _ => Seq() + }) } + case _ => Seq() + })) ++ Seq() + + case t: Field => + Seq(Definition(t.name, "Field", t.pos)) + + } flatten) toList + } + + private def countInstances(p: Program): Map[String, Int] = p.members.groupBy({ + case m: Method => "method" + case fu: Function => "function" + case p: Predicate => "predicate" + case d: Domain => "domain" + case fi: Field => "field" + case _ => "other" + }).mapValues(_.size) + + def reportProgramStats(): Unit = { + val prog = _frontend.program.get + val stats = countInstances(prog) + + _frontend.reporter.report(ProgramOutlineReport(prog.members.toList)) + _frontend.reporter.report(StatisticsReport( + stats.getOrElse("method", 0), + stats.getOrElse("function", 0), + stats.getOrElse("predicate", 0), + stats.getOrElse("domain", 0), + stats.getOrElse("field", 0) + )) + _frontend.reporter.report(ProgramDefinitionsReport(collect(prog))) + } +} \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/Envelope.scala b/src/main/scala/viper/server/vsi/Envelope.scala index 4121b3c..2d7df97 100644 --- a/src/main/scala/viper/server/vsi/Envelope.scala +++ b/src/main/scala/viper/server/vsi/Envelope.scala @@ -12,18 +12,12 @@ package viper.server.vsi * */ trait Envelope -sealed trait Post { +trait Post { type A -} - -trait Packer extends Post { /** Must be implemented to map client-specific messages to Envelopes. * */ def pack(m: A): Envelope -} - -trait Unpacker extends Post { /** Must be implemented to map Envelopes to client-specific messages. * */ diff --git a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala index b671e84..9874bc3 100644 --- a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -20,7 +20,7 @@ import scala.concurrent.duration._ * The first serves the purpose of running the process concurrently. The second allows to * communicate from the verification process to the server. * */ -abstract class MessageStreamingTask[T]()(implicit val executionContext: ExecutionContext) extends Runnable with Packer { +abstract class MessageStreamingTask[T]()(implicit val executionContext: ExecutionContext) extends Runnable with Post { def artifact: Future[T] @@ -36,11 +36,11 @@ abstract class MessageStreamingTask[T]()(implicit val executionContext: Executio * which will eventually indicate whether or not the offer was successful. This method is * blocking, as it waits for the successful completion of such an offer. * */ - protected def enqueueMessages(msg: A): Unit = { + protected def enqueueMessage(msg: Envelope): Unit = { implicit val askTimeout: Timeout = Timeout(5000 milliseconds) var current_offer: Future[QueueOfferResult] = null - val answer = q_actor ? TaskProtocol.BackendReport(pack(msg)) + val answer = q_actor ? TaskProtocol.BackendReport(msg) current_offer = answer.flatMap({ case res: Future[QueueOfferResult] => res }) diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index fbdd375..d17e873 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -36,7 +36,7 @@ case class JobNotFoundException() extends VerificationServerException * between a VerificationTask's backend and the server. The Terminator Actor is in charge of * terminating both individual processes and the server. */ -trait VerificationServer extends Unpacker { +trait VerificationServer extends Post { type AST diff --git a/src/test/scala/CoreServerTests.scala b/src/test/scala/CoreServerTests.scala index 2a8c2de..d3af453 100644 --- a/src/test/scala/CoreServerTests.scala +++ b/src/test/scala/CoreServerTests.scala @@ -50,7 +50,7 @@ class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { private val silent_logger = SilentLogger() - private val ast_gen = new AstGenerator(silent_logger) + private val ast_gen = new AstGenerator(silent_logger.get) private val empty_file = "src/test/resources/viper/empty.vpr" private val sum_file = "src/test/resources/viper/sum_method.vpr" private val verificationError_file = "src/test/resources/viper/verification_error.vpr" diff --git a/src/test/scala/ParsingTests.scala b/src/test/scala/ParsingTests.scala index b53dac4..1b3dec5 100644 --- a/src/test/scala/ParsingTests.scala +++ b/src/test/scala/ParsingTests.scala @@ -27,7 +27,7 @@ class ParsingTests extends WordSpec with Matchers with ScalatestRouteTest { "AstGenerator" should { var ast_gen: AstGenerator = null s"should be instantiated without errors" in { - ast_gen = new AstGenerator(console_logger) + ast_gen = new AstGenerator(console_logger.get) } var test_ast: Option[Program] = null From 8f9d54cd7884c559c889f90d9a7d6428f2880fe9 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 16:57:05 +0100 Subject: [PATCH 24/79] Get rid of language definitions reporting in VerificationWorker. Add generic method VerificationServer.initializeProcess. Fix typos. Add stubs for the AST construction request. --- .../server/core/VerificationWorker.scala | 108 ------------------ .../viper/server/core/ViperCoreServer.scala | 24 +++- .../frontends/http/ViperHttpServer.scala | 6 +- .../frontends/lsp/ViperServerService.scala | 9 +- src/main/scala/viper/server/vsi/HTTP.scala | 39 +++++-- .../scala/viper/server/vsi/JobActor.scala | 74 +++++++----- src/main/scala/viper/server/vsi/JobPool.scala | 14 ++- .../scala/viper/server/vsi/Protocol.scala | 28 ++++- .../scala/viper/server/vsi/QueueActor.scala | 6 +- .../scala/viper/server/vsi/Terminator.scala | 22 ++-- .../viper/server/vsi/VerificationServer.scala | 93 ++++++++++----- 11 files changed, 228 insertions(+), 195 deletions(-) diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index ddbff70..b785ba4 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -118,112 +118,6 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program s"ViperBackend( ${_frontend.verifier.name} )" } - private def collectDefinitions(program: Program): List[Definition] = { - (program.members.collect { - case t: Method => - (Definition(t.name, "Method", t.pos) +: (t.pos match { - case p: AbstractSourcePosition => - t.formalArgs.map { arg => Definition(arg.name, "Argument", arg.pos, Some(p)) } ++ - t.formalReturns.map { arg => Definition(arg.name, "Return", arg.pos, Some(p)) } - case _ => Seq() - })) ++ t.deepCollectInBody { - case scope: Scope with Positioned => - scope.pos match { - case p: AbstractSourcePosition => - scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } - case _ => Seq() - } - }.flatten - - case t: Function => - (Definition(t.name, "Function", t.pos) +: (t.pos match { - case p: AbstractSourcePosition => - t.formalArgs.map { arg => Definition(arg.name, "Argument", arg.pos, Some(p)) } - case _ => Seq() - })) ++ (t.body match { - case Some(exp) => - exp.deepCollect { - case scope:Scope with Positioned => - scope.pos match { - case p: AbstractSourcePosition => - scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } - case _ => Seq() - } - } flatten - case _ => Seq() - }) - - case t: Predicate => - (Definition(t.name, "Predicate", t.pos) +: (t.pos match { - case p: AbstractSourcePosition => - t.formalArgs.map { arg => Definition(arg.name, "Argument", arg.pos, Some(p)) } - case _ => Seq() - })) ++ (t.body match { - case Some(exp) => - exp.deepCollect { - case scope:Scope with Positioned => - scope.pos match { - case p: AbstractSourcePosition => - scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } - case _ => Seq() - } - } flatten - case _ => Seq() - }) - - case t: Domain => - (Definition(t.name, "Domain", t.pos) +: (t.pos match { - case p: AbstractSourcePosition => - t.functions.flatMap { func => - Definition(func.name, "Function", func.pos, Some(p)) +: (func.pos match { - case func_p: AbstractSourcePosition => - func.formalArgs.map { arg => Definition(if (arg.isInstanceOf[LocalVarDecl]) arg.asInstanceOf[LocalVarDecl].name else "unnamed parameter", "Argument", arg.pos, Some(func_p)) } - case _ => Seq() - }) - } ++ t.axioms.flatMap { ax => - Definition(if (ax.isInstanceOf[NamedDomainAxiom]) ax.asInstanceOf[NamedDomainAxiom].name else "", "Axiom", ax.pos, Some(p)) +: (ax.pos match { - case ax_p: AbstractSourcePosition => - ax.exp.deepCollect { - case scope:Scope with Positioned => - scope.pos match { - case p: AbstractSourcePosition => - scope.scopedDecls.map { local_decl => Definition(local_decl.name, "Local", local_decl.pos, Some(p)) } - case _ => Seq() - } - } flatten - case _ => Seq() - }) } - case _ => Seq() - })) ++ Seq() - - case t: Field => - Seq(Definition(t.name, "Field", t.pos)) - - } flatten) toList - } - - private def countInstances(p: Program): Map[String, Int] = p.members.groupBy({ - case m: Method => "method" - case fu: Function => "function" - case p: Predicate => "predicate" - case d: Domain => "domain" - case fi: Field => "field" - case _ => "other" - }).mapValues(_.size) - - private def reportProgramStats(prog: Program): Unit = { - val stats = countInstances(prog) - _frontend.reporter.report(ProgramOutlineReport(prog.members.toList)) - _frontend.reporter.report(StatisticsReport( - stats.getOrElse("method", 0), - stats.getOrElse("function", 0), - stats.getOrElse("predicate", 0), - stats.getOrElse("domain", 0), - stats.getOrElse("field", 0) - )) - _frontend.reporter.report(ProgramDefinitionsReport(collectDefinitions(prog))) - } - /** Run the backend verification functionality * */ def execute(args: Seq[String]){ @@ -235,8 +129,6 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program if (!_frontend.prepare(fileless_args)) return _frontend.init( _frontend.verifier ) - reportProgramStats(_ast) - val temp_result: Option[VerificationResult] = if (_frontend.config.disableCaching()) { _frontend.logger.info("Verification with caching disabled") Some(_frontend.verifier.verify(_ast)) diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index dc74032..a2ecda0 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -46,6 +46,28 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with println(s"ViperCoreServer started.") } + + + +// def requestAst(input: String): AstJobId = { +// require(config != null) +// +// if (!ast_jobs.newJobsAllowed) { +// logger.get.error(s"Could not start AST construction process. " + +// s"The maximum number of active jobs are currently running (${ast_jobs.MAX_ACTIVE_JOBS}).") +// return AstJobId(-1) +// } +// +// val task_backend = new AstWorker(input, logger.get) +// val ast_id = initializeAstConstruction(Future.successful(task_backend)) +// ast_jobs.run_job(ast_id) +// +// if (ast_id.id >= 0) { +// logger.get.info(s"AST construction of $ast_id has successfully started.") +// } +// ast_id +// } + /** Verifies a Viper AST using the specified backend. * * Expects a non-null backend config and Viper AST. @@ -64,7 +86,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with logger.get.info(s"Verification process #${jid.id} has successfully started.") } else { logger.get.error(s"Could not start verification process. " + - s"The maximum number of active verification jobs are currently running (${jobs.MAX_ACTIVE_JOBS}).") + s"The maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") } jid } diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 01788da..07f2b9a 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -26,7 +26,7 @@ import viper.server.vsi.Requests.CacheResetRequest import viper.server.vsi._ import viper.silver.ast.Program import viper.silver.logger.ViperLogger -import viper.silver.reporter.{Message, Reporter} +import viper.silver.reporter.Message import scala.util.{Failure, Success, Try} @@ -98,8 +98,8 @@ class ViperHttpServer(_args: Array[String]) VerificationRequestAccept(jid.id) } else { logger.get.error(s"Could not start verification process. " + - s"The maximum number of active verification jobs are currently running (${jobs.MAX_ACTIVE_JOBS}).") - VerificationRequestReject(s"the maximum number of active verification jobs are currently running (${jobs.MAX_ACTIVE_JOBS}).") + s"The maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") + VerificationRequestReject(s"the maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") } } diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index bd29441..67778e8 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -18,10 +18,9 @@ import viper.server.core.{ViperCache, ViperCoreServer} import viper.server.frontends.lsp.VerificationState.Stopped import viper.server.utility.AstGenerator import viper.server.utility.Helpers.getArgListFromArgString -import viper.server.vsi.VerificationProtocol.Stop +import viper.server.vsi.VerificationProtocol.StopVerification import viper.server.vsi.{VerJobId, VerificationServer} import viper.silver.ast.Program -import viper.silver.reporter.{Message, Reporter} import scala.compat.java8.FutureConverters._ import scala.concurrent.Future @@ -99,7 +98,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with Log.info(s"Verification process #${jid.id} has successfully started.") } else { Log.debug(s"Could not start verification process. " + - s"the maximum number of active verification jobs are currently running (${jobs.MAX_ACTIVE_JOBS}).") + s"the maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") } jid } @@ -111,11 +110,11 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with } def stopVerification(jid: VerJobId): CFuture[Boolean] = { - jobs.lookupJob(jid) match { + ver_jobs.lookupJob(jid) match { case Some(handle_future) => handle_future.flatMap(handle => { implicit val askTimeout: Timeout = Timeout(config.actorCommunicationTimeout() milliseconds) - val interrupt: Future[String] = (handle.job_actor ? Stop()).mapTo[String] + val interrupt: Future[String] = (handle.job_actor ? StopVerification).mapTo[String] handle.job_actor ! PoisonPill // the actor played its part. interrupt }).toJava.toCompletableFuture.thenApply(msg => { diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index 775ad95..5e10e49 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -16,7 +16,7 @@ import akka.http.scaladsl.server.Route import akka.pattern.ask import akka.stream.scaladsl.Source import akka.util.Timeout -import viper.server.vsi.VerificationProtocol.Stop +import viper.server.vsi.VerificationProtocol.StopVerification import scala.concurrent.Future import scala.concurrent.duration._ @@ -63,9 +63,10 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { var bindingFuture: Future[Http.ServerBinding] = _ override def start(active_jobs: Int): Unit = { - jobs = new JobPool[VerJobId, VerHandle](active_jobs) - bindingFuture = Http().bindAndHandle(setRoutes, "localhost", port) - _termActor = system.actorOf(Terminator.props(jobs, Some(bindingFuture)), "terminator") + ast_jobs = new JobPool("AST-pool", active_jobs) + ver_jobs = new JobPool("Verification-pool", active_jobs) + bindingFuture = Http().bindAndHandle(setRoutes(), "localhost", port) + _termActor = system.actorOf(Terminator.props(ast_jobs, ver_jobs, Some(bindingFuture)), "terminator") isRunning = true } @@ -113,23 +114,43 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { complete(onVerifyPost(r)) } } + } ~ path("ast" / IntNumber) { jid => + + get { + ast_jobs.lookupJob(AstJobId(jid)) match { + case Some(handle_future) => + onComplete(handle_future) { + case Success(handle) => + val s = Source.fromPublisher(handle.publisher) + _termActor ! Terminator.WatchJobQueue(VerJobId(jid), handle) + complete(unpackMessages(s)) + case Failure(error) => + // TODO use AST-specific response + complete(verificationRequestRejection(jid, error)) + } + case None => + // TODO use AST-specific response + complete(verificationRequestRejection(jid, JobNotFoundException())) + } + } + } ~ path("verify" / IntNumber) { jid => /** Send GET request to "/verify/" where is a non-negative integer. * must be an ID of an existing verification job. */ get { - jobs.lookupJob(VerJobId(jid)) match { + ver_jobs.lookupJob(VerJobId(jid)) match { case Some(handle_future) => // Found a job with this jid. onComplete(handle_future) { case Success(handle) => - val s: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) + val s: Source[Envelope, NotUsed] = Source.fromPublisher(handle.publisher) _termActor ! Terminator.WatchJobQueue(VerJobId(jid), handle) complete(unpackMessages(s)) case Failure(error) => complete(verificationRequestRejection(jid, error)) } - case _ => + case None => complete(verificationRequestRejection(jid, JobNotFoundException())) } } @@ -138,12 +159,12 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { * must be an ID of an existing verification job. */ get { - jobs.lookupJob(VerJobId(jid)) match { + ver_jobs.lookupJob(VerJobId(jid)) match { case Some(handle_future) => onComplete(handle_future) { case Success(handle) => implicit val askTimeout: Timeout = Timeout(5000 milliseconds) - val interrupt_done: Future[String] = (handle.job_actor ? Stop).mapTo[String] + val interrupt_done: Future[String] = (handle.job_actor ? StopVerification).mapTo[String] onSuccess(interrupt_done) { msg => handle.job_actor ! PoisonPill // the actor played its part. complete(discardJobConfirmation(jid, msg)) diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index c72dfc7..07aa3ad 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -1,54 +1,76 @@ package viper.server.vsi import akka.actor.{Actor, Props} -import akka.stream.scaladsl.SourceQueueWithComplete -import org.reactivestreams.Publisher // --- Actor: JobActor --- object JobActor { - def props(id: VerJobId): Props = Props(new JobActor(id)) + def props(id: JobId): Props = Props(new JobActor(id)) } -class JobActor(private val id: VerJobId) extends Actor { +class JobActor(private val id: JobId) extends Actor { + import VerificationProtocol._ + + private var _astConstructionTask: Thread = _ private var _verificationTask: Thread = _ - private def interrupt: Boolean = { - if (_verificationTask != null && _verificationTask.isAlive) { - _verificationTask.interrupt() - _verificationTask.join() + private def interrupt(task: Thread): Boolean = { + if (task != null && task.isAlive) { + task.interrupt() + task.join() return true } false } - private def resetVerificationTask(): Unit = { - if (_verificationTask != null && _verificationTask.isAlive) { - _verificationTask.interrupt() - _verificationTask.join() + private def resetTask(task: Thread): Unit = { + if (task != null && task.isAlive) { + task.interrupt() + task.join() } + } + + private def resetAstConstructionTask(): Unit = { + resetTask(_astConstructionTask) + _astConstructionTask = null + } + + private def resetVerificationTask(): Unit = { + resetTask(_verificationTask) _verificationTask = null } override def receive: PartialFunction[Any, Unit] = { - case VerificationProtocol.Stop => - val did_I_interrupt = interrupt + case req: StartProcessRequest => + req match { + case _: ConstructAst => + println(">>> JobActor received request ConstructAst") + resetAstConstructionTask() + _astConstructionTask = req.task + _astConstructionTask.start() + sender ! AstHandle(self, req.queue, req.publisher) + case _: Verify => + println(">>> JobActor received request Verify") + resetVerificationTask() + _verificationTask = req.task + _verificationTask.start() + sender ! VerHandle(self, req.queue, req.publisher) + } + case req: StopProcessRequest => + val did_I_interrupt = req match { + case StopAstConstruction => + interrupt(_astConstructionTask) + case StopVerification => + interrupt(_verificationTask) + } if (did_I_interrupt) { - sender ! s"Job #$id has been successfully interrupted." + sender ! s"$id has been successfully interrupted." } else { - sender ! s"Job #$id has already been finalized." + // FIXME: Saying this is a potential vulnerability + sender ! s"$id has already been finalized." } - case VerificationProtocol.Verify(task, queue, publisher) => - resetVerificationTask() - sender ! startJob(task, queue, publisher) case msg => - throw new Exception("Main Actor: unexpected message received: " + msg) - } - - private def startJob(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]): VerHandle = { - _verificationTask = task - _verificationTask.start() - VerHandle(self, queue, publisher) + throw new Exception("JobActor: received unexpected message: " + msg) } } \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index c4f8801..b7ba7a7 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -13,8 +13,12 @@ sealed trait JobId { override def toString: String = s"${tag}_id_${id}" } +case class AstJobId(id: Int) extends JobId { + def tag = "AST" +} + case class VerJobId(id: Int) extends JobId { - def tag = "ver" + def tag = "VER" } sealed trait JobHandle { @@ -24,13 +28,19 @@ sealed trait JobHandle { val publisher: Publisher[Envelope] } +case class AstHandle(job_actor: ActorRef, + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope]) extends JobHandle { + def tag = "AST" +} + case class VerHandle(job_actor: ActorRef, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]) extends JobHandle { def tag = "VER" } -class JobPool[S <: JobId, T <: JobHandle](val MAX_ACTIVE_JOBS: Int = 3) +class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: Int = 3) (implicit val jid_fact: Int => S) { private val _jobHandles: mutable.Map[S, Promise[T]] = mutable.Map() diff --git a/src/main/scala/viper/server/vsi/Protocol.scala b/src/main/scala/viper/server/vsi/Protocol.scala index 0541a0f..6de8531 100644 --- a/src/main/scala/viper/server/vsi/Protocol.scala +++ b/src/main/scala/viper/server/vsi/Protocol.scala @@ -16,16 +16,34 @@ object TaskProtocol { case class FinalBackendReport(success: Boolean) } -// Protocol to start/stop verification process. object VerificationProtocol { - // Request Job Actor to execute verification task - case class Verify(task: Thread, queue: SourceQueueWithComplete[Envelope], publisher: Publisher[Envelope]) + sealed trait StartProcessRequest { + val task: Thread + val queue: SourceQueueWithComplete[Envelope] + val publisher: Publisher[Envelope] + } - // Verification interrupt request to Terminator Actor - case class Stop() + // Request Job Actor to execute an AST construction task + case class ConstructAst(task: Thread, + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope]) extends StartProcessRequest + + // Request Job Actor to execute a verification task + case class Verify(task: Thread, + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope]) extends StartProcessRequest + + sealed trait StopProcessRequest + + // Request Job Actor to stop its verification task + case object StopAstConstruction extends StopProcessRequest + + // Request Job Actor to stop its verification task + case object StopVerification extends StopProcessRequest } + object Requests extends akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport with DefaultJsonProtocol { case class VerificationRequest(arg: String) diff --git a/src/main/scala/viper/server/vsi/QueueActor.scala b/src/main/scala/viper/server/vsi/QueueActor.scala index 0aa74f3..6b9c318 100644 --- a/src/main/scala/viper/server/vsi/QueueActor.scala +++ b/src/main/scala/viper/server/vsi/QueueActor.scala @@ -6,11 +6,11 @@ import akka.stream.scaladsl.SourceQueueWithComplete // --- Actor: MessageActor --- object QueueActor { - def props(jid: VerJobId, queue: SourceQueueWithComplete[Envelope]): Props = - Props(new QueueActor(jid, queue)) + def props(queue: SourceQueueWithComplete[Envelope]): Props = + Props(new QueueActor(queue)) } -class QueueActor(jid: VerJobId, queue: SourceQueueWithComplete[Envelope]) extends Actor { +class QueueActor(queue: SourceQueueWithComplete[Envelope]) extends Actor { override def receive: PartialFunction[Any, Unit] = { case TaskProtocol.BackendReport(msg) => diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala index 92e9d96..ddd633f 100644 --- a/src/main/scala/viper/server/vsi/Terminator.scala +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -12,14 +12,17 @@ import scala.util.{Failure, Success} object Terminator { case object Exit - case class WatchJobQueue(jid: VerJobId, handle: VerHandle) + case class WatchJobQueue(jid: JobId, handle: JobHandle) - def props(jobs: JobPool[VerJobId, VerHandle], + def props(ast_jobs: JobPool[AstJobId, AstHandle], + ver_jobs: JobPool[VerJobId, VerHandle], bindingFuture: Option[Future[Http.ServerBinding]] = None) - (implicit ctx: ExecutionContext, sys: ActorSystem): Props = Props(new Terminator(jobs, bindingFuture)(ctx, sys)) + (implicit ctx: ExecutionContext, sys: ActorSystem): Props + = Props(new Terminator(ast_jobs, ver_jobs, bindingFuture)(ctx, sys)) } -class Terminator(jobs: JobPool[VerJobId, VerHandle], +class Terminator(ast_jobs: JobPool[AstJobId, AstHandle], + ver_jobs: JobPool[VerJobId, VerHandle], bindingFuture: Option[Future[Http.ServerBinding]]) (implicit val ctx: ExecutionContext, implicit val sys: ActorSystem) extends Actor { @@ -38,11 +41,16 @@ class Terminator(jobs: JobPool[VerJobId, VerHandle], } case Terminator.WatchJobQueue(jid, handle) => val queue_completion_future: Future[Done] = handle.queue.watchCompletion() - queue_completion_future.onComplete( { + queue_completion_future.onComplete { case Failure(e) => throw e case Success(_) => - jobs.discardJob(jid) - }) + jid match { + case ast_id: AstJobId => + ast_jobs.discardJob(ast_id) + case ver_id: VerJobId => + ver_jobs.discardJob(ver_id) + } + } } } \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index d17e873..76cf9ba 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -15,6 +15,7 @@ import akka.util.Timeout import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} +import scala.reflect.ClassTag import scala.util.{Failure, Success} @@ -43,10 +44,15 @@ trait VerificationServer extends Post { implicit val system: ActorSystem = ActorSystem("Main") implicit val executionContext: ExecutionContextExecutor = ExecutionContext.global implicit val materializer: ActorMaterializer = ActorMaterializer() + protected var _termActor: ActorRef = _ - implicit val jid_fact: Int => VerJobId = VerJobId.apply - protected var jobs: JobPool[VerJobId, VerHandle] = _ + implicit val ast_id_fact: Int => AstJobId = AstJobId.apply + implicit val ver_id_fact: Int => VerJobId = VerJobId.apply + + protected var ast_jobs: JobPool[AstJobId, AstHandle] = _ + protected var ver_jobs: JobPool[VerJobId, VerHandle] = _ + var isRunning: Boolean = false /** Configures an instance of VerificationServer. @@ -55,37 +61,72 @@ trait VerificationServer extends Post { * will result in an IllegalStateException. * */ def start(active_jobs: Int): Unit = { - jobs = new JobPool(active_jobs) - _termActor = system.actorOf(Terminator.props(jobs), "terminator") + ast_jobs = new JobPool("Http-AST-pool", active_jobs) + ver_jobs = new JobPool("Http-Verification-pool", active_jobs) + _termActor = system.actorOf(Terminator.props(ast_jobs, ver_jobs), "terminator") isRunning = true } + protected def initializeProcess[S <: JobId, T <: JobHandle : ClassTag] + (pool: JobPool[S, T], + task_fut: Future[MessageStreamingTask[AST]], + actor_fut_maybe: Option[Future[ActorRef]] = None): S = { + + if (!isRunning) { + throw new IllegalStateException("Instance of VerificationServer already stopped") + } + + require(pool.newJobsAllowed) + + /** Ask the pool to book a new job using the above function + * to construct Future[JobHandle] and Promise[AST] later on. */ + pool.bookNewJob((new_jid: JobId) => { + + /** TODO avoid hardcoded parameters */ + implicit val askTimeout: Timeout = Timeout(5000 milliseconds) + + /** What we really want here is SourceQueueWithComplete[Envelope] + * Publisher[Envelope] might be needed to create a stream later on, + * but the publisher and the queue are synchronized are should be viewed + * as different representation of the same concept. + */ + val (queue, publisher) = Source.queue[Envelope](10000, OverflowStrategy.backpressure) + .toMat(Sink.asPublisher(false))(Keep.both).run() + + /** This actor will be responsible for managing ONE queue, + * whereas the JobActor can manage multiple tasks, all of which are related to some pipeline, + * e.g. [Text] ---> [AST] ---> [VerificationResult] + * '--- Task I ----' | + * '---------- Task II ----------' + **/ + val message_actor = system.actorOf(QueueActor.props(queue), s"${pool.tag}_message_actor_${new_jid}") + + task_fut.map(task => { + task.setQueueActor(message_actor) + + val job_actor = system.actorOf(JobActor.props(new_jid), s"${pool.tag}_job_actor_${new_jid}") + + (job_actor ? (new_jid match { + case _: AstJobId => + VerificationProtocol.ConstructAst(new Thread(task), queue, publisher) + case _: VerJobId => + VerificationProtocol.Verify(new Thread(task), queue, publisher) + })).mapTo[T] + }).flatten + }) + } + /** This method starts a verification process. * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. */ protected def initializeVerificationProcess(task: MessageStreamingTask[AST]): VerJobId = { - if(!isRunning) { + if (!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") } - if (jobs.newJobsAllowed) { - def createJob(new_jid: VerJobId): Future[VerHandle] = { - - implicit val askTimeout: Timeout = Timeout(5000 milliseconds) - val job_actor = system.actorOf(JobActor.props(new_jid), s"job_actor_$new_jid") - val (queue, publisher) = Source.queue[Envelope](10000, OverflowStrategy.backpressure) - .toMat(Sink.asPublisher(false))(Keep.both) - .run() - val message_actor = system.actorOf(QueueActor.props(new_jid, queue), s"queue_actor_$new_jid") - task.setQueueActor(message_actor) - val task_with_actor = new Thread(task) - val answer = job_actor ? VerificationProtocol.Verify(task_with_actor, queue, publisher) - val new_job_handle: Future[VerHandle] = answer.mapTo[VerHandle] - new_job_handle - } - val id = jobs.bookNewJob(createJob) - id + if (ver_jobs.newJobsAllowed) { + initializeProcess(ver_jobs, Future.successful(task), None) } else { VerJobId(-1) // Process Management running at max capacity. } @@ -96,11 +137,11 @@ trait VerificationServer extends Post { * Deletes the JobHandle on completion. */ protected def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Unit]] = { - if(!isRunning) { + if (!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") } - jobs.lookupJob(jid) match { + ver_jobs.lookupJob(jid) match { case Some(handle_future) => def mapHandle(handle: VerHandle): Future[Unit] = { val src_envelope: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) @@ -138,11 +179,11 @@ trait VerificationServer extends Post { /** This method interrupts active jobs upon termination of the server. */ protected def getInterruptFutureList(): Future[List[String]] = { - val interrupt_future_list: List[Future[String]] = jobs.jobHandles map { case (jid, handle_future) => + val interrupt_future_list: List[Future[String]] = ver_jobs.jobHandles map { case (jid, handle_future) => handle_future.flatMap { case VerHandle(actor, _, _) => implicit val askTimeout: Timeout = Timeout(1000 milliseconds) - (actor ? VerificationProtocol.Stop).mapTo[String] + (actor ? VerificationProtocol.StopVerification).mapTo[String] } } toList val overall_interrupt_future: Future[List[String]] = Future.sequence(interrupt_future_list) From 1204cc7ac3e9b5495eaac330fe1a7a0005cfdbb5 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 18:30:16 +0100 Subject: [PATCH 25/79] Use helpers from ViperBackendConfig more often. initializeVerificationProcess now takes Future[MessageStreamingTask[AST]] --- .../server/core/ViperBackendConfig.scala | 38 ++++++++++++++++--- .../viper/server/core/ViperCoreServer.scala | 13 ++----- .../frontends/http/ViperHttpServer.scala | 16 ++++---- .../frontends/lsp/ViperServerService.scala | 14 ++++--- .../viper/server/vsi/VerificationServer.scala | 4 +- src/test/scala/CoreServerTests.scala | 1 - 6 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/main/scala/viper/server/core/ViperBackendConfig.scala b/src/main/scala/viper/server/core/ViperBackendConfig.scala index c357edc..225c018 100644 --- a/src/main/scala/viper/server/core/ViperBackendConfig.scala +++ b/src/main/scala/viper/server/core/ViperBackendConfig.scala @@ -6,14 +6,42 @@ package viper.server.core + +import viper.server.utility.Helpers.getArgListFromArgString + + trait ViperBackendConfig { + val backend_name: String val partialCommandLine: List[String] + + def toList: List[String] = this match { + case _ : SiliconConfig => "silicon" :: partialCommandLine + case _ : CarbonConfig => "carbon" :: partialCommandLine + case _ : CustomConfig => "custom" :: partialCommandLine + } } -object ViperBackendConfigs { - object EmptyConfig extends ViperBackendConfig {val partialCommandLine: List[String] = Nil} +//object EmptyConfig extends ViperBackendConfig { +// override val backend_name = "empty_config" +// val partialCommandLine: List[String] = Nil +//} +case class SiliconConfig(partialCommandLine: List[String]) extends ViperBackendConfig { + override val backend_name = "silicon" +} +case class CarbonConfig(partialCommandLine: List[String]) extends ViperBackendConfig { + override val backend_name = "carbon" +} +case class CustomConfig(partialCommandLine: List[String], backend_name: String) extends ViperBackendConfig + +object ViperBackendConfig { + + def apply(args: List[String]): ViperBackendConfig = args match { + // case Nil => EmptyConfig + case "silicon" :: args => SiliconConfig(args) + case "carbon" :: args => CarbonConfig(args) + case custom :: args => CustomConfig(args, custom) + case invalid => throw new IllegalArgumentException(s"cannot build ViperConfig from string `$invalid`") + } - case class SiliconConfig(partialCommandLine: List[String]) extends ViperBackendConfig - case class CarbonConfig(partialCommandLine: List[String]) extends ViperBackendConfig - case class CustomConfig(partialCommandLine: List[String]) extends ViperBackendConfig + def apply(input: String): ViperBackendConfig = apply(getArgListFromArgString(input)) } \ No newline at end of file diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index a2ecda0..1abc1d6 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -8,7 +8,6 @@ package viper.server.core import akka.actor.ActorRef import viper.server.ViperConfig -import viper.server.core.ViperBackendConfigs._ import viper.server.vsi.{VerJobId, VerificationServer} import viper.silver.ast.Program import viper.silver.logger.ViperLogger @@ -72,16 +71,12 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with * * Expects a non-null backend config and Viper AST. * */ - def verify(programID: String, backend_config: ViperBackendConfig, program: Program): VerJobId = { + def verify(programId: String, backend_config: ViperBackendConfig, program: Program): VerJobId = { require(program != null && backend_config != null) - val args: List[String] = backend_config match { - case _ : SiliconConfig => "silicon" :: backend_config.partialCommandLine - case _ : CarbonConfig => "carbon" :: backend_config.partialCommandLine - case _ : CustomConfig => "custom" :: backend_config.partialCommandLine - } - val task_backend = new VerificationWorker(_config, logger.get, args :+ programID, program) - val jid = initializeVerificationProcess(task_backend) + val args: List[String] = backend_config.toList + val task_backend = new VerificationWorker(_config, logger.get, args :+ programId, program) + val jid = initializeVerificationProcess(Future.successful(task_backend)) if(jid.id >= 0) { logger.get.info(s"Verification process #${jid.id} has successfully started.") } else { diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 07f2b9a..451cbd2 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -17,8 +17,7 @@ import edu.mit.csail.sdg.parser.CompUtil import edu.mit.csail.sdg.translator.{A4Options, TranslateAlloyToKodkod} import spray.json.DefaultJsonProtocol import viper.server.ViperConfig -import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} -import viper.server.core.{ViperCache, ViperCoreServer} +import viper.server.core.{ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} import viper.server.utility.AstGenerator import viper.server.utility.Helpers.getArgListFromArgString @@ -67,7 +66,7 @@ class ViperHttpServer(_args: Array[String]) // Extract file name from args list val arg_list = getArgListFromArgString(vr.arg) val file: String = arg_list.last - val arg_list_partial = arg_list.dropRight(1) + val arg_list_partial: List[String] = arg_list.dropRight(1) // Parse file val astGen = new AstGenerator(_logger.get) @@ -81,12 +80,11 @@ class ViperHttpServer(_args: Array[String]) val ast = ast_option.getOrElse(return VerificationRequestReject("The file for which verification has been requested contained syntax errors.")) // prepare backend config - val backend = arg_list_partial match { - case "silicon" :: args => SiliconConfig(args) - case "carbon" :: args => CarbonConfig(args) - case "custom" :: args => CustomConfig(args) - case args => - logger.get.error(s"Invalid arguments: ${args.toString} " + + val backend = try { + ViperBackendConfig(arg_list_partial) + } catch { + case _: IllegalArgumentException => + logger.get.error(s"Invalid arguments: ${vr.arg} " + s"You need to specify the verification backend, e.g., `silicon [args]`") return VerificationRequestReject("Invalid arguments for backend.") } diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 67778e8..8b18237 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -13,8 +13,7 @@ import java.util.concurrent.{CompletableFuture => CFuture} import akka.actor.{PoisonPill, Props} import akka.pattern.ask import akka.util.Timeout -import viper.server.core.ViperBackendConfigs.{CarbonConfig, CustomConfig, SiliconConfig} -import viper.server.core.{ViperCache, ViperCoreServer} +import viper.server.core.{ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.lsp.VerificationState.Stopped import viper.server.utility.AstGenerator import viper.server.utility.Helpers.getArgListFromArgString @@ -87,10 +86,13 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with }) // prepare backend config - val backend = arg_list_partial match { - case "silicon" :: args => SiliconConfig(args) - case "carbon" :: args => CarbonConfig(args) - case "custom" :: args => CustomConfig(args) + val backend = try { + ViperBackendConfig(arg_list_partial) + } catch { + case _: IllegalArgumentException => + logger.get.error(s"Invalid arguments: ${command} " + + s"You need to specify the verification backend, e.g., `silicon [args]`") + return VerJobId(-1) } val jid: VerJobId = verify(file, backend, ast) diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 76cf9ba..1307d50 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -120,13 +120,13 @@ trait VerificationServer extends Post { * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. */ - protected def initializeVerificationProcess(task: MessageStreamingTask[AST]): VerJobId = { + protected def initializeVerificationProcess(task_fut: Future[MessageStreamingTask[AST]]): VerJobId = { if (!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") } if (ver_jobs.newJobsAllowed) { - initializeProcess(ver_jobs, Future.successful(task), None) + initializeProcess(ver_jobs, task_fut, None) } else { VerJobId(-1) // Process Management running at max capacity. } diff --git a/src/test/scala/CoreServerTests.scala b/src/test/scala/CoreServerTests.scala index d3af453..8a2cd3e 100644 --- a/src/test/scala/CoreServerTests.scala +++ b/src/test/scala/CoreServerTests.scala @@ -9,7 +9,6 @@ package viper.server.core import akka.actor.{Actor, ActorSystem, Props} import akka.http.scaladsl.testkit.ScalatestRouteTest import org.scalatest.{Matchers, WordSpec} -import viper.server.core.ViperBackendConfigs.SiliconConfig import viper.server.vsi._ import viper.server.utility.AstGenerator import viper.silver.ast.Program From ba597e3985390f6b730918a20d7b08aa3b303747 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 18:39:34 +0100 Subject: [PATCH 26/79] Use custom TaskThread type for managing job artifacts in JobActor. --- .../scala/viper/server/vsi/JobActor.scala | 30 ++++++++++++------- .../scala/viper/server/vsi/Protocol.scala | 12 ++++---- .../viper/server/vsi/VerificationServer.scala | 4 +-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index 07aa3ad..9e4c2b8 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -2,20 +2,29 @@ package viper.server.vsi import akka.actor.{Actor, Props} +import scala.concurrent.Future + + +class TaskThread[T](private val _task: MessageStreamingTask[T]) extends Thread(_task) { + def getArtifact: Future[T] = _task.artifact +} + // --- Actor: JobActor --- object JobActor { - def props(id: JobId): Props = Props(new JobActor(id)) + def props[T](id: JobId): Props = Props(new JobActor[T](id)) } -class JobActor(private val id: JobId) extends Actor { +class JobActor[T](private val id: JobId) extends Actor { import VerificationProtocol._ - private var _astConstructionTask: Thread = _ - private var _verificationTask: Thread = _ - private def interrupt(task: Thread): Boolean = { + + private var _astConstructionTask: TaskThread[T] = _ + private var _verificationTask: TaskThread[T] = _ + + private def interrupt(task: TaskThread[T]): Boolean = { if (task != null && task.isAlive) { task.interrupt() task.join() @@ -24,7 +33,7 @@ class JobActor(private val id: JobId) extends Actor { false } - private def resetTask(task: Thread): Unit = { + private def resetTask(task: TaskThread[T]): Unit = { if (task != null && task.isAlive) { task.interrupt() task.join() @@ -42,15 +51,16 @@ class JobActor(private val id: JobId) extends Actor { } override def receive: PartialFunction[Any, Unit] = { - case req: StartProcessRequest => + case req: StartProcessRequest[T] => req match { - case _: ConstructAst => + case _: ConstructAst[T] => println(">>> JobActor received request ConstructAst") resetAstConstructionTask() _astConstructionTask = req.task _astConstructionTask.start() - sender ! AstHandle(self, req.queue, req.publisher) - case _: Verify => + sender ! AstHandle(self, req.queue, req.publisher/*, _astConstructionTask.getArtifact*/) + + case _: Verify[T] => println(">>> JobActor received request Verify") resetVerificationTask() _verificationTask = req.task diff --git a/src/main/scala/viper/server/vsi/Protocol.scala b/src/main/scala/viper/server/vsi/Protocol.scala index 6de8531..3374a3a 100644 --- a/src/main/scala/viper/server/vsi/Protocol.scala +++ b/src/main/scala/viper/server/vsi/Protocol.scala @@ -18,21 +18,21 @@ object TaskProtocol { object VerificationProtocol { - sealed trait StartProcessRequest { - val task: Thread + sealed trait StartProcessRequest[T] { + val task: TaskThread[T] val queue: SourceQueueWithComplete[Envelope] val publisher: Publisher[Envelope] } // Request Job Actor to execute an AST construction task - case class ConstructAst(task: Thread, + case class ConstructAst[T](task: TaskThread[T], queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) extends StartProcessRequest + publisher: Publisher[Envelope]) extends StartProcessRequest[T] // Request Job Actor to execute a verification task - case class Verify(task: Thread, + case class Verify[T](task: TaskThread[T], queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) extends StartProcessRequest + publisher: Publisher[Envelope]) extends StartProcessRequest[T] sealed trait StopProcessRequest diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 1307d50..9fdce8e 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -108,9 +108,9 @@ trait VerificationServer extends Post { (job_actor ? (new_jid match { case _: AstJobId => - VerificationProtocol.ConstructAst(new Thread(task), queue, publisher) + VerificationProtocol.ConstructAst(new TaskThread(task), queue, publisher) case _: VerJobId => - VerificationProtocol.Verify(new Thread(task), queue, publisher) + VerificationProtocol.Verify(new TaskThread(task), queue, publisher) })).mapTo[T] }).flatten }) From 91ed5ba3db34fb8b2fd4f22e1aa2339addecbd9d Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 24 Nov 2020 18:44:34 +0100 Subject: [PATCH 27/79] Store a Future[AST] in each AstHandle instance. --- src/main/scala/viper/server/vsi/JobActor.scala | 2 +- src/main/scala/viper/server/vsi/JobPool.scala | 7 ++++--- src/main/scala/viper/server/vsi/Terminator.scala | 12 ++++++------ .../scala/viper/server/vsi/VerificationServer.scala | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index 9e4c2b8..3e81471 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -58,7 +58,7 @@ class JobActor[T](private val id: JobId) extends Actor { resetAstConstructionTask() _astConstructionTask = req.task _astConstructionTask.start() - sender ! AstHandle(self, req.queue, req.publisher/*, _astConstructionTask.getArtifact*/) + sender ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact) case _: Verify[T] => println(">>> JobActor received request Verify") diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index b7ba7a7..0348b6d 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -28,9 +28,10 @@ sealed trait JobHandle { val publisher: Publisher[Envelope] } -case class AstHandle(job_actor: ActorRef, - queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) extends JobHandle { +case class AstHandle[R](job_actor: ActorRef, + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope], + artifact: Future[R]) extends JobHandle { def tag = "AST" } diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala index ddd633f..3e88dad 100644 --- a/src/main/scala/viper/server/vsi/Terminator.scala +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -14,16 +14,16 @@ object Terminator { case object Exit case class WatchJobQueue(jid: JobId, handle: JobHandle) - def props(ast_jobs: JobPool[AstJobId, AstHandle], - ver_jobs: JobPool[VerJobId, VerHandle], - bindingFuture: Option[Future[Http.ServerBinding]] = None) + def props[R](ast_jobs: JobPool[AstJobId, AstHandle[R]], + ver_jobs: JobPool[VerJobId, VerHandle], + bindingFuture: Option[Future[Http.ServerBinding]] = None) (implicit ctx: ExecutionContext, sys: ActorSystem): Props = Props(new Terminator(ast_jobs, ver_jobs, bindingFuture)(ctx, sys)) } -class Terminator(ast_jobs: JobPool[AstJobId, AstHandle], - ver_jobs: JobPool[VerJobId, VerHandle], - bindingFuture: Option[Future[Http.ServerBinding]]) +class Terminator[R](ast_jobs: JobPool[AstJobId, AstHandle[R]], + ver_jobs: JobPool[VerJobId, VerHandle], + bindingFuture: Option[Future[Http.ServerBinding]]) (implicit val ctx: ExecutionContext, implicit val sys: ActorSystem) extends Actor { diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 9fdce8e..bd14be2 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -50,7 +50,7 @@ trait VerificationServer extends Post { implicit val ast_id_fact: Int => AstJobId = AstJobId.apply implicit val ver_id_fact: Int => VerJobId = VerJobId.apply - protected var ast_jobs: JobPool[AstJobId, AstHandle] = _ + protected var ast_jobs: JobPool[AstJobId, AstHandle[AST]] = _ protected var ver_jobs: JobPool[VerJobId, VerHandle] = _ var isRunning: Boolean = false From aa6951abed06d5f005ba443676e289a28f40c0e7 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 25 Nov 2020 14:38:15 +0100 Subject: [PATCH 28/79] Added method initializeAstConstruction to VSI --- src/main/scala/viper/server/vsi/HTTP.scala | 36 ++++++++++--------- src/main/scala/viper/server/vsi/JobPool.scala | 23 +++++++++--- .../server/vsi/MessageStreamingTask.scala | 1 + .../scala/viper/server/vsi/QueueActor.scala | 3 +- .../viper/server/vsi/VerificationServer.scala | 14 +++++++- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index 5e10e49..379be39 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -114,52 +114,54 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { complete(onVerifyPost(r)) } } - } ~ path("ast" / IntNumber) { jid => - + } ~ path("ast" / IntNumber) { id => + val ast_id = AstJobId(id) get { - ast_jobs.lookupJob(AstJobId(jid)) match { + ast_jobs.lookupJob(ast_id) match { case Some(handle_future) => onComplete(handle_future) { case Success(handle) => - val s = Source.fromPublisher(handle.publisher) - _termActor ! Terminator.WatchJobQueue(VerJobId(jid), handle) + val s: Source[Envelope, NotUsed] = Source.fromPublisher(handle.publisher) + _termActor ! Terminator.WatchJobQueue(ast_id, handle) complete(unpackMessages(s)) case Failure(error) => // TODO use AST-specific response - complete(verificationRequestRejection(jid, error)) + complete(verificationRequestRejection(id, error)) } case None => // TODO use AST-specific response - complete(verificationRequestRejection(jid, JobNotFoundException())) + complete(verificationRequestRejection(id, JobNotFoundException())) } } - } ~ path("verify" / IntNumber) { jid => + } ~ path("verify" / IntNumber) { id => /** Send GET request to "/verify/" where is a non-negative integer. * must be an ID of an existing verification job. */ + val ver_id = VerJobId(id) get { - ver_jobs.lookupJob(VerJobId(jid)) match { + ver_jobs.lookupJob(ver_id) match { case Some(handle_future) => // Found a job with this jid. onComplete(handle_future) { case Success(handle) => val s: Source[Envelope, NotUsed] = Source.fromPublisher(handle.publisher) - _termActor ! Terminator.WatchJobQueue(VerJobId(jid), handle) + _termActor ! Terminator.WatchJobQueue(ver_id, handle) complete(unpackMessages(s)) case Failure(error) => - complete(verificationRequestRejection(jid, error)) + complete(verificationRequestRejection(id, error)) } case None => - complete(verificationRequestRejection(jid, JobNotFoundException())) + complete(verificationRequestRejection(id, JobNotFoundException())) } } - } ~ path("discard" / IntNumber) { jid => + } ~ path("discard" / IntNumber) { id => /** Send GET request to "/discard/" where is a non-negative integer. * must be an ID of an existing verification job. */ + val ver_id = VerJobId(id) get { - ver_jobs.lookupJob(VerJobId(jid)) match { + ver_jobs.lookupJob(ver_id) match { case Some(handle_future) => onComplete(handle_future) { case Success(handle) => @@ -167,13 +169,13 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { val interrupt_done: Future[String] = (handle.job_actor ? StopVerification).mapTo[String] onSuccess(interrupt_done) { msg => handle.job_actor ! PoisonPill // the actor played its part. - complete(discardJobConfirmation(jid, msg)) + complete(discardJobConfirmation(id, msg)) } case Failure(_) => - complete(discardJobRejection(jid)) + complete(discardJobRejection(id)) } case _ => - complete(discardJobRejection(jid)) + complete(discardJobRejection(id)) } } } diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index 0348b6d..7f9f94f 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -5,7 +5,7 @@ import akka.stream.scaladsl.SourceQueueWithComplete import org.reactivestreams.Publisher import scala.collection.mutable -import scala.concurrent.{Future, Promise} +import scala.concurrent.{ExecutionContext, Future, Promise} sealed trait JobId { val id: Int @@ -14,11 +14,11 @@ sealed trait JobId { } case class AstJobId(id: Int) extends JobId { - def tag = "AST" + def tag = "ast" } case class VerJobId(id: Int) extends JobId { - def tag = "VER" + def tag = "ver" } sealed trait JobHandle { @@ -42,10 +42,12 @@ case class VerHandle(job_actor: ActorRef, } class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: Int = 3) - (implicit val jid_fact: Int => S) { + (implicit val jid_fact: Int => S, + ctx: ExecutionContext) { private val _jobHandles: mutable.Map[S, Promise[T]] = mutable.Map() private val _jobExecutors: mutable.Map[S, () => Future[T]] = mutable.Map() + private val _jobCache: mutable.Map[S, Future[T]] = mutable.Map() def jobHandles: Map[S, Future[T]] = _jobHandles.map{ case (id, hand) => (id, hand.future) }.toMap private var _nextJobId: Int = 0 @@ -58,7 +60,16 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: val new_jid: S = jid_fact(_nextJobId) _jobHandles(new_jid) = Promise() - _jobExecutors(new_jid) = () => job_executor(new_jid) + _jobExecutors(new_jid) = () => { + if (_jobCache.contains(new_jid)) { + /** This prevents recomputing the same future multiple times. */ + _jobCache(new_jid) + } else { + val t_fut = job_executor(new_jid) + _jobCache(new_jid) = t_fut + t_fut + } + } _nextJobId = _nextJobId + 1 new_jid @@ -66,6 +77,8 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: def discardJob(jid: S): Unit = { _jobHandles -= jid + _jobExecutors -= jid + _jobCache -= jid } def lookupJob(jid: S): Option[Future[T]] = { diff --git a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala index 9874bc3..bb73513 100644 --- a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -37,6 +37,7 @@ abstract class MessageStreamingTask[T]()(implicit val executionContext: Executio * blocking, as it waits for the successful completion of such an offer. * */ protected def enqueueMessage(msg: Envelope): Unit = { + // FIXME ATG: this method needs a review implicit val askTimeout: Timeout = Timeout(5000 milliseconds) var current_offer: Future[QueueOfferResult] = null diff --git a/src/main/scala/viper/server/vsi/QueueActor.scala b/src/main/scala/viper/server/vsi/QueueActor.scala index 6b9c318..4a44e1a 100644 --- a/src/main/scala/viper/server/vsi/QueueActor.scala +++ b/src/main/scala/viper/server/vsi/QueueActor.scala @@ -6,8 +6,7 @@ import akka.stream.scaladsl.SourceQueueWithComplete // --- Actor: MessageActor --- object QueueActor { - def props(queue: SourceQueueWithComplete[Envelope]): Props = - Props(new QueueActor(queue)) + def props(queue: SourceQueueWithComplete[Envelope]): Props = Props(new QueueActor(queue)) } class QueueActor(queue: SourceQueueWithComplete[Envelope]) extends Actor { diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index bd14be2..99e865f 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -99,7 +99,7 @@ trait VerificationServer extends Post { * '--- Task I ----' | * '---------- Task II ----------' **/ - val message_actor = system.actorOf(QueueActor.props(queue), s"${pool.tag}_message_actor_${new_jid}") + val message_actor = system.actorOf(QueueActor.props(queue), s"${pool.tag}--message_actor--${new_jid.id}") task_fut.map(task => { task.setQueueActor(message_actor) @@ -116,6 +116,18 @@ trait VerificationServer extends Post { }) } + protected def initializeAstConstruction(task: MessageStreamingTask[AST]): AstJobId = { + if (!isRunning) { + throw new IllegalStateException("Instance of VerificationServer already stopped") + } + + if (ast_jobs.newJobsAllowed) { + initializeProcess(ast_jobs, Future.successful(task), None) + } else { + AstJobId(-1) // Process Management running at max capacity. + } + } + /** This method starts a verification process. * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. From 7d6b4db6abecbf60ea12c0d3e69205a0db48ad74 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 25 Nov 2020 14:45:09 +0100 Subject: [PATCH 29/79] (1) Properly handle exceptions in AstWorker. (2) ViperHttpServer.onVerifyPost now completes the /verify request with ast_id and ver_id. The former can be used for streaming AST construction results. The latter can be used for streaming the verification results. (3) Added json writers for AstConstructionResultMessage instances. --- .../scala/viper/server/core/AstWorker.scala | 40 +++++++--- .../server/core/VerificationWorker.scala | 13 +--- .../viper/server/core/ViperCoreServer.scala | 73 +++++++++++++------ .../frontends/http/ViperHttpServer.scala | 30 +------- .../http/jsonWriters/ViperIDEProtocol.scala | 47 ++++++++++-- .../viper/server/utility/AstGenerator.scala | 35 +-------- 6 files changed, 129 insertions(+), 109 deletions(-) diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala index f075e82..9fc72ed 100644 --- a/src/main/scala/viper/server/core/AstWorker.scala +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -5,10 +5,12 @@ import ch.qos.logback.classic.Logger import viper.server.utility.AstGenerator import viper.server.utility.Helpers.getArgListFromArgString import viper.silver.ast.Program +import viper.silver.reporter.ExceptionReport abstract class AstConstructionException extends Exception object ViperFileNotFoundException extends AstConstructionException +object AstConstructionInterrupted extends AstConstructionException object InvalidArgumentsException extends AstConstructionException object AstConstructionFailureException extends AstConstructionException object OutOfResourcesException extends AstConstructionException @@ -32,23 +34,41 @@ class AstWorker(val input: String, val arg_list = getArgListFromArgString(input) val file: String = arg_list.last - val astGen = new AstGenerator(logger, new ActorReporter("AstGenerationReporter")) - var ast_option: Option[Program] = None - try { - ast_option = astGen.generateViperAst(file) + val reporter = new ActorReporter("AstGenerationReporter") + val astGen = new AstGenerator(logger, reporter) + + val ast_option: Option[Program] = try { + astGen.generateViperAst(file) } catch { case _: java.nio.file.NoSuchFileException => println("The file for which verification has been requested was not found.") + registerTaskEnd(false) throw ViperFileNotFoundException + case e: InterruptedException => + logger.info(s"AstWorker has been interrupted: $e") + registerTaskEnd(false) + throw AstConstructionInterrupted + case e: java.nio.channels.ClosedByInterruptException => + logger.info(s"AstWorker has been interrupted: $e") + registerTaskEnd(false) + throw AstConstructionInterrupted + case e: Throwable => + reporter report ExceptionReport(e) + logger.trace(s"Creation/Execution of an AstGenerator instance resulted in an exception.", e) + registerTaskEnd(false) + throw ServerCrashException(e) } - val ast = ast_option match { - case Some(a) => - a - case _ => - println("The file for which verification has been requested contained syntax errors.") + + ast_option match { + case Some(ast) => + registerTaskEnd(true) + ast + case None => + logger.info("The file for which verification has been requested contained syntax errors, type errors, " + + "or is simply inconsistent.") + registerTaskEnd(false) throw AstConstructionFailureException } - ast } override def run(): Unit = { diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index b785ba4..215f582 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -8,7 +8,6 @@ package viper.server.core import ch.qos.logback.classic.Logger import viper.carbon.CarbonFrontend -import viper.server.ViperConfig import viper.server.vsi.Envelope import viper.silicon.SiliconFrontend import viper.silver.ast._ @@ -30,8 +29,7 @@ case class ViperServerBackendNotFoundException(name: String) extends ViperServer case class ViperEnvelope(m: Message) extends Envelope -class VerificationWorker(private val viper_config: ViperConfig, - private val logger: Logger, +class VerificationWorker(private val logger: Logger, private val command: List[String], private val program: Program)(implicit val ec: ExecutionContext) extends MessageReportingTask { @@ -81,7 +79,8 @@ class VerificationWorker(private val viper_config: ViperConfig, case _: java.nio.channels.ClosedByInterruptException => case e: Throwable => enqueueMessage(ExceptionReport(e)) - logger.trace(s"Creation/Execution of the verification backend ${if (backend == null) "" else backend.toString} resulted in exception.", e) + logger.trace(s"Creation/Execution of the verification backend " + + s"${if (backend == null) "" else backend.toString} resulted in an exception.", e) } finally { try { backend.stop() @@ -98,12 +97,6 @@ class VerificationWorker(private val viper_config: ViperConfig, registerTaskEnd(false) } } - - override type A = Message - - override def pack(m: A): Envelope = { - ViperEnvelope(m) - } } class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program) { diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 1abc1d6..d4fa2e1 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -8,7 +8,7 @@ package viper.server.core import akka.actor.ActorRef import viper.server.ViperConfig -import viper.server.vsi.{VerJobId, VerificationServer} +import viper.server.vsi.{AstHandle, AstJobId, VerJobId, VerificationServer} import viper.silver.ast.Program import viper.silver.logger.ViperLogger @@ -45,27 +45,51 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with println(s"ViperCoreServer started.") } + def requestAst(input: String): AstJobId = { + require(config != null) + val task_backend = new AstWorker(input, logger.get) + val ast_id = initializeAstConstruction(task_backend) + if (ast_id.id >= 0) { + logger.get.info(s"Verification process #${ast_id.id} has successfully started.") + } else { + logger.get.error(s"Could not start verification process. " + + s"The maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") + } + ast_id + } -// def requestAst(input: String): AstJobId = { -// require(config != null) -// -// if (!ast_jobs.newJobsAllowed) { -// logger.get.error(s"Could not start AST construction process. " + -// s"The maximum number of active jobs are currently running (${ast_jobs.MAX_ACTIVE_JOBS}).") -// return AstJobId(-1) -// } -// -// val task_backend = new AstWorker(input, logger.get) -// val ast_id = initializeAstConstruction(Future.successful(task_backend)) -// ast_jobs.run_job(ast_id) -// -// if (ast_id.id >= 0) { -// logger.get.info(s"AST construction of $ast_id has successfully started.") -// } -// ast_id -// } + def verify(ast_id: AstJobId, backend_config: ViperBackendConfig): VerJobId = { + + if (!isRunning) throw new IllegalStateException("Instance of VerificationServer already stopped") + require(backend_config != null) + + val programId = s"ViperAst#${ast_id.id}" + val args: List[String] = backend_config.toList + + ast_jobs.lookupJob(ast_id) match { + case Some(handle_future) => + val task_backend_fut = + handle_future.map((handle: AstHandle[Program]) => { + val art = handle.artifact + art.map(program => { + new VerificationWorker(logger.get, args :+ programId, program) + }).recover({ + case e: Throwable => + println(s"### As exception has occurred while constructing Viper AST: $e") + throw e + }) + + }).flatten + + initializeVerificationProcess(task_backend_fut) + + case None => + logger.get.error(s"Could not start verification process for non-existent $ast_id") + VerJobId(-1) + } + } /** Verifies a Viper AST using the specified backend. * @@ -75,15 +99,16 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with require(program != null && backend_config != null) val args: List[String] = backend_config.toList - val task_backend = new VerificationWorker(_config, logger.get, args :+ programId, program) - val jid = initializeVerificationProcess(Future.successful(task_backend)) - if(jid.id >= 0) { - logger.get.info(s"Verification process #${jid.id} has successfully started.") + val task_backend = new VerificationWorker(logger.get, args :+ programId, program) + val ver_id = initializeVerificationProcess(Future.successful(task_backend)) + + if (ver_id.id >= 0) { + logger.get.info(s"Verification process #${ver_id.id} has successfully started.") } else { logger.get.error(s"Could not start verification process. " + s"The maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") } - jid + ver_id } override def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Unit]] = { diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 451cbd2..3f46603 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -19,11 +19,9 @@ import spray.json.DefaultJsonProtocol import viper.server.ViperConfig import viper.server.core.{ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} -import viper.server.utility.AstGenerator import viper.server.utility.Helpers.getArgListFromArgString import viper.server.vsi.Requests.CacheResetRequest import viper.server.vsi._ -import viper.silver.ast.Program import viper.silver.logger.ViperLogger import viper.silver.reporter.Message @@ -63,23 +61,10 @@ class ViperHttpServer(_args: Array[String]) } override def onVerifyPost(vr: Requests.VerificationRequest): ToResponseMarshallable = { - // Extract file name from args list + val ast_id = requestAst(vr.arg) + val arg_list = getArgListFromArgString(vr.arg) - val file: String = arg_list.last val arg_list_partial: List[String] = arg_list.dropRight(1) - - // Parse file - val astGen = new AstGenerator(_logger.get) - var ast_option: Option[Program] = None - try { - ast_option = astGen.generateViperAst(file) - } catch { - case _: java.nio.file.NoSuchFileException => - return VerificationRequestReject("The file for which verification has been requested was not found.") - } - val ast = ast_option.getOrElse(return VerificationRequestReject("The file for which verification has been requested contained syntax errors.")) - - // prepare backend config val backend = try { ViperBackendConfig(arg_list_partial) } catch { @@ -89,16 +74,9 @@ class ViperHttpServer(_args: Array[String]) return VerificationRequestReject("Invalid arguments for backend.") } - val jid: VerJobId = verify(file, backend, ast) + val ver_id = verify(ast_id, backend) - if (jid.id >= 0) { - logger.get.info(s"Verification process #${jid.id} has successfully started.") - VerificationRequestAccept(jid.id) - } else { - logger.get.error(s"Could not start verification process. " + - s"The maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") - VerificationRequestReject(s"the maximum number of active verification jobs are currently running (${ver_jobs.MAX_ACTIVE_JOBS}).") - } + VerificationRequestAccept(ast_id, ver_id) } override def unpackMessages(s: Source[Envelope, NotUsed]): ToResponseMarshallable = { diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index ec46b4e..53106d3 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -6,6 +6,7 @@ import akka.stream.scaladsl.Flow import akka.util.ByteString import edu.mit.csail.sdg.translator.A4Solution import spray.json.DefaultJsonProtocol +import viper.server.vsi.{AstJobId, VerJobId} import viper.silicon.SymbLog import viper.silicon.state.terms.Term import viper.silver.reporter._ @@ -15,7 +16,7 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs import spray.json._ - final case class VerificationRequestAccept(id: Int) + final case class VerificationRequestAccept(ast_id: AstJobId, ver_id: VerJobId) final case class VerificationRequestReject(msg: String) final case class ServerStopConfirmed(msg: String) final case class JobDiscardAccept(msg: String) @@ -26,7 +27,12 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs final case class AlloyGenerationRequestComplete(solution: A4Solution) - implicit val verReqAccept_format: RootJsonFormat[VerificationRequestAccept] = jsonFormat1(VerificationRequestAccept.apply) + implicit val verReqAccept_writer: RootJsonFormat[VerificationRequestAccept] = lift(new RootJsonWriter[VerificationRequestAccept] { + override def write(obj: VerificationRequestAccept): JsValue = JsObject( + "ast_id" -> JsNumber(obj.ast_id.id), + "ver_id" -> JsNumber(obj.ver_id.id) + ) + }) implicit val verReqReject_format: RootJsonFormat[VerificationRequestReject] = jsonFormat1(VerificationRequestReject.apply) implicit val serverStopConfirmed_format: RootJsonFormat[ServerStopConfirmed] = jsonFormat1(ServerStopConfirmed.apply) implicit val jobDiscardAccept_format: RootJsonFormat[JobDiscardAccept] = jsonFormat1(JobDiscardAccept.apply) @@ -124,11 +130,36 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs "cached" -> obj.cached.toJson) }) + implicit val astConstructionMessage_writer = lift(new RootJsonWriter[AstConstructionResultMessage] { + override def write(obj: AstConstructionResultMessage): JsValue = JsObject( + "status" -> (obj match { + case _: AstConstructionSuccessMessage => + JsString("success") + case _: AstConstructionFailureMessage => + JsString("failure") + }), + "details" -> (obj match { + case succ: AstConstructionSuccessMessage => + succ.toJson + case fail: AstConstructionFailureMessage => + fail.toJson + })) + }) + + implicit val astConstructionSuccess_writer: RootJsonFormat[AstConstructionSuccessMessage] = lift(new RootJsonWriter[AstConstructionSuccessMessage] { + override def write(obj: AstConstructionSuccessMessage): JsValue = JsObject( + "time" -> obj.astConstructionTime.toJson) + }) + + implicit val astConstructionFailure_writer: RootJsonFormat[AstConstructionFailureMessage] = lift(new RootJsonWriter[AstConstructionFailureMessage] { + override def write(obj: AstConstructionFailureMessage): JsValue = JsObject( + "time" -> obj.astConstructionTime.toJson, + "result" -> obj.result.toJson) + }) + implicit val overallSuccessMessage_writer: RootJsonFormat[OverallSuccessMessage] = lift(new RootJsonWriter[OverallSuccessMessage] { - override def write(obj: OverallSuccessMessage): JsObject = { - JsObject( - "time" -> obj.verificationTime.toJson) - } + override def write(obj: OverallSuccessMessage): JsObject = JsObject( + "time" -> obj.verificationTime.toJson) }) implicit val overallFailureMessage_writer: RootJsonFormat[OverallFailureMessage] = lift(new RootJsonWriter[OverallFailureMessage] { @@ -208,7 +239,8 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs }) implicit val programDefinitionsReport_writer: RootJsonFormat[ProgramDefinitionsReport] = lift(new RootJsonWriter[ProgramDefinitionsReport] { - override def write(obj: ProgramDefinitionsReport) = JsObject("definitions" -> JsArray(obj.definitions.map(_.toJson).toVector)) + override def write(obj: ProgramDefinitionsReport) = JsObject( + "definitions" -> JsArray(obj.definitions.map(_.toJson).toVector)) }) implicit val symbExLogReport_writer: RootJsonFormat[ExecutionTraceReport] = lift(new RootJsonWriter[ExecutionTraceReport] { @@ -282,6 +314,7 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs override def write(obj: Message): JsValue = JsObject( "msg_type" -> JsString(obj.name), "msg_body" -> (obj match { + case p: AstConstructionResultMessage => p.toJson case a: VerificationResultMessage => a.toJson case s: StatisticsReport => s.toJson case o: ProgramOutlineReport => o.toJson diff --git a/src/main/scala/viper/server/utility/AstGenerator.scala b/src/main/scala/viper/server/utility/AstGenerator.scala index 5faf116..da4563a 100644 --- a/src/main/scala/viper/server/utility/AstGenerator.scala +++ b/src/main/scala/viper/server/utility/AstGenerator.scala @@ -9,7 +9,6 @@ package viper.server.utility import ch.qos.logback.classic.Logger import viper.silver.ast.Program import viper.silver.frontend.{SilFrontend, ViperAstProvider} -import viper.silver.parser.PProgram import viper.silver.reporter.{NoopReporter, Reporter} class AstGenerator(private val _logger: Logger, @@ -21,47 +20,19 @@ class AstGenerator(private val _logger: Logger, _logger.info(s"Creating new verification backend.") new ViperAstProvider(_reporter) } - /** Parses and translates a Viper file into a Viper AST. * * Throws an exception when passed an inexistent file! */ def generateViperAst(vpr_file_path: String): Option[Program] = { val args: Array[String] = Array(vpr_file_path) - _logger.info(s"Parsing viper file.") + _logger.info(s"Parsing Viper file ...") + _frontend.execute(args) - if (_frontend.errors.isEmpty) { + if (_frontend.program.isDefined) { reportProgramStats() - Some(_frontend.translationResult) - } else { - None - } - } - - /** Parses a Viper file - */ - private def parse(): Option[PProgram] = { - _frontend.parsing() - if(_frontend.errors.isEmpty) { - _logger.info("There was no error while parsing!") - Some(_frontend.parsingResult) - } else { - _logger.error(s"There was some error while parsing: ${_frontend.errors}") - None } - } - - /** Translates a Parsed Viper file into a Viper AST - */ - private def translate(): Option[Program] = { - _logger.info(s"Translating parsed file.") - _frontend.semanticAnalysis() - _frontend.translation() - _frontend.consistencyCheck() - _frontend.verifier.stop() - if (_frontend.errors.isEmpty) { - _logger.info("There was no error while translating!") Some(_frontend.translationResult) } else { _logger.error(s"There was some error while translating ${_frontend.errors}") From 69e6ec419a644c54e9df0a7e7a2bfc5fcdd32efa Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 25 Nov 2020 17:51:56 +0100 Subject: [PATCH 30/79] Support SMT counterexamples in ViperHttpServer --- .../server/core/MessageReportingTask.scala | 2 +- .../http/jsonWriters/ViperIDEProtocol.scala | 53 ++++++++++++++++--- .../viper/server/utility/AstGenerator.scala | 2 +- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/main/scala/viper/server/core/MessageReportingTask.scala b/src/main/scala/viper/server/core/MessageReportingTask.scala index f2caa15..9106341 100644 --- a/src/main/scala/viper/server/core/MessageReportingTask.scala +++ b/src/main/scala/viper/server/core/MessageReportingTask.scala @@ -17,7 +17,7 @@ trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost val name = s"ViperServer_$tag" def report(msg: Message): Unit = { - println(s">>> ActorReporter.report($msg)") + //println(s">>> ActorReporter.report($msg)") enqueueMessage(msg) } } diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index 53106d3..131a7b8 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -96,18 +96,55 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs } }) + implicit val modelEntry_writer: RootJsonFormat[ModelEntry] = lift(new RootJsonWriter[ModelEntry] { + override def write(obj: ModelEntry): JsValue = obj match { + case SingleEntry(value: String) => + JsString(value) + case MapEntry(options, els) => + JsObject( + "cases" -> JsObject(options.map { + case (args: Seq[String], res: String) => + (res, JsArray(args.map(_.toJson).toVector)) }), + "else" -> JsString(els) + ) + } + }) + + implicit val model_writer: RootJsonFormat[Model] = lift(new RootJsonWriter[Model] { + override def write(obj: Model): JsValue = + JsObject(obj.entries.map { case (k: String, v: ModelEntry) => (k, v.toJson) }) + }) + + implicit val counterexample_writer: RootJsonFormat[Counterexample] = lift(new RootJsonWriter[Counterexample] { + override def write(obj: Counterexample): JsValue = JsObject("model" -> obj.model.toJson) + }) + implicit val abstractError_writer: RootJsonFormat[AbstractError] = lift(new RootJsonWriter[AbstractError] { - override def write(obj: AbstractError) = JsObject( - "tag" -> JsString(obj.fullId), - "text" -> JsString(obj.readableMessage), - "position" -> (obj.pos match { - case src_pos: Position => src_pos.toJson - case no_pos => JsString(no_pos.toString) - })) + override def write(obj: AbstractError) = { + obj match { + case e: VerificationError if e.counterexample.isDefined => + JsObject( + "tag" -> JsString(obj.fullId), + "text" -> JsString(obj.readableMessage), + "position" -> (obj.pos match { + case src_pos: Position => src_pos.toJson + case no_pos => JsString(no_pos.toString) + }), + "counterexample" -> e.counterexample.get.toJson) + case _ => + JsObject( + "tag" -> JsString(obj.fullId), + "text" -> JsString(obj.readableMessage), + "position" -> (obj.pos match { + case src_pos: Position => src_pos.toJson + case no_pos => JsString(no_pos.toString) + })) + } + } }) implicit val failure_writer: RootJsonFormat[Failure] = lift(new RootJsonWriter[Failure] { - override def write(obj: Failure) = + override def write(obj: Failure): JsObject = JsObject( "type" -> JsString("error"), "errors" -> JsArray(obj.errors.map(_.toJson).toVector)) diff --git a/src/main/scala/viper/server/utility/AstGenerator.scala b/src/main/scala/viper/server/utility/AstGenerator.scala index da4563a..4113988 100644 --- a/src/main/scala/viper/server/utility/AstGenerator.scala +++ b/src/main/scala/viper/server/utility/AstGenerator.scala @@ -22,7 +22,7 @@ class AstGenerator(private val _logger: Logger, } /** Parses and translates a Viper file into a Viper AST. * - * Throws an exception when passed an inexistent file! + * Throws an exception when passed an non-existent file! */ def generateViperAst(vpr_file_path: String): Option[Program] = { val args: Array[String] = Array(vpr_file_path) From ec38a62a80cb88645165d203b4739dc6755c4cb5 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Sat, 28 Nov 2020 12:53:03 +0100 Subject: [PATCH 31/79] Tiny changes in VSI --- src/main/scala/viper/server/vsi/Cache.scala | 11 +++++------ src/main/scala/viper/server/vsi/JobPool.scala | 5 ++++- .../scala/viper/server/vsi/MessageStreamingTask.scala | 2 +- src/main/scala/viper/server/vsi/Terminator.scala | 6 +++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/scala/viper/server/vsi/Cache.scala b/src/main/scala/viper/server/vsi/Cache.scala index 97c7d80..ddcfb08 100644 --- a/src/main/scala/viper/server/vsi/Cache.scala +++ b/src/main/scala/viper/server/vsi/Cache.scala @@ -46,10 +46,10 @@ abstract class Cache { * The inner map is referred to as the fileCache. As the name indicates, it stores, for each * file, a number of hashes and corresponding cache entries. */ - protected val _cache = MutableMap[String, FileCash]() + protected val _cache: MutableMap[String, FileCash] = MutableMap() type FileCash = MutableMap[String, List[CacheEntry]] - protected val _program_cache = MutableMap[Ast, MutableMap[CacheableMember, List[Member]]]() + protected val _program_cache: MutableMap[Ast, MutableMap[CacheableMember, List[Member]]] = MutableMap() /** This method transforms a program and returns verification results based on the cache's * current state. @@ -113,10 +113,9 @@ abstract class Cache { /** Utility function to retrieve entries for single members. * */ - final def get( - file_key: String, - key: CacheableMember, - dependencies: List[Member]): Option[CacheEntry] = { + final def get(file_key: String, + key: CacheableMember, + dependencies: List[Member]): Option[CacheEntry] = { val concerning_hash = key.hash val dependencies_hash = dependencies.map(_.hash).mkString(" ") diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index 7f9f94f..8dc405e 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -84,7 +84,10 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: def lookupJob(jid: S): Option[Future[T]] = { _jobHandles.get(jid).map((promise: Promise[T]) => { promise.completeWith(_jobExecutors(jid)()) + + //promise.future.map(_.queue.watchCompletion().onComplete(_ => discardJob(jid))) + promise.future }) } -} \ No newline at end of file +} diff --git a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala index bb73513..7b237ec 100644 --- a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -45,7 +45,7 @@ abstract class MessageStreamingTask[T]()(implicit val executionContext: Executio current_offer = answer.flatMap({ case res: Future[QueueOfferResult] => res }) - while(current_offer == null || !current_offer.isCompleted){ + while (current_offer == null || !current_offer.isCompleted) { Thread.sleep(10) } } diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala index 3e88dad..4a949f8 100644 --- a/src/main/scala/viper/server/vsi/Terminator.scala +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -3,9 +3,9 @@ package viper.server.vsi import akka.Done import akka.actor.{Actor, ActorSystem, Props} import akka.http.scaladsl.Http +import scala.util.{Failure, Success} import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} // --- Actor: Terminator --- @@ -47,8 +47,12 @@ class Terminator[R](ast_jobs: JobPool[AstJobId, AstHandle[R]], case Success(_) => jid match { case ast_id: AstJobId => + // TODO: Use logger + //println(s"Terminator: Discarding AST construction job $ast_id") ast_jobs.discardJob(ast_id) case ver_id: VerJobId => + // TODO: Use logger + //println(s"Terminator: Discarding verification job $ver_id") ver_jobs.discardJob(ver_id) } } From 403658ee08d6bcdde89a0d3c3291b86629aeed95 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Sat, 28 Nov 2020 13:04:34 +0100 Subject: [PATCH 32/79] Improvements in ViperServer's caching mechanism: - Fixed a bug that resulted in repetition of reported cached messages. - Fixed a bug that resulted in duplication of reported errors (overall vs. for_entity) - Fixed a logging bug in doCachedVerification - Add flag "cached" to the marshalled representation of AbstractError. - Added stubs for caching functions and predicates --- .../server/core/MessageReportingTask.scala | 14 ++++-- .../server/core/VerificationWorker.scala | 25 ++++++---- .../scala/viper/server/core/ViperCache.scala | 50 ++++++++++++++++--- .../viper/server/core/ViperCoreServer.scala | 2 +- .../frontends/http/ViperHttpServer.scala | 4 +- .../http/jsonWriters/ViperIDEProtocol.scala | 6 ++- 6 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/main/scala/viper/server/core/MessageReportingTask.scala b/src/main/scala/viper/server/core/MessageReportingTask.scala index 9106341..5236f08 100644 --- a/src/main/scala/viper/server/core/MessageReportingTask.scala +++ b/src/main/scala/viper/server/core/MessageReportingTask.scala @@ -2,7 +2,7 @@ package viper.server.core import viper.server.vsi.MessageStreamingTask import viper.silver.ast.Program -import viper.silver.reporter.{Message, Reporter} +import viper.silver.reporter.{EntityFailureMessage, EntitySuccessMessage, Message, Reporter} trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost { @@ -17,8 +17,16 @@ trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost val name = s"ViperServer_$tag" def report(msg: Message): Unit = { - //println(s">>> ActorReporter.report($msg)") - enqueueMessage(msg) + println(s">>> ActorReporter.report($msg)") + msg match { + case m: EntityFailureMessage if m.concerning.info.isCached => + case m: EntitySuccessMessage if m.concerning.info.isCached => + // Do not re-send messages about AST nodes that have been cached; + // the information about these nodes is going to be reported anyway. + + case m => + enqueueMessage(m) + } } } diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index 215f582..d5f39d6 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -31,7 +31,8 @@ case class ViperEnvelope(m: Message) extends Envelope class VerificationWorker(private val logger: Logger, private val command: List[String], - private val program: Program)(implicit val ec: ExecutionContext) extends MessageReportingTask { + private val program: Program)(implicit val ec: ExecutionContext) + extends MessageReportingTask { override def artifact: Future[Program] = Future.successful(program) private var backend: ViperBackend = _ @@ -113,7 +114,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program /** Run the backend verification functionality * */ - def execute(args: Seq[String]){ + def execute(args: Seq[String]) { val (head, tail) = args.splitAt(args.length-1) val fileless_args = head ++ Seq("--ignoreFile") ++ tail _frontend.setStartTime() @@ -154,28 +155,32 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program // collect and report errors val all_cached_errors: collection.mutable.ListBuffer[VerificationError] = ListBuffer() - cached_results.foreach (result => { + cached_results.foreach((result: CacheResult) => { val cached_errors = result.verification_errors - if(cached_errors.isEmpty){ - _frontend.reporter.report(CachedEntityMessage(_frontend.getVerifierName,result.method, Success)) + if (cached_errors.isEmpty) { + _frontend.reporter report + CachedEntityMessage(_frontend.getVerifierName,result.method, Success) } else { all_cached_errors ++= cached_errors - _frontend.reporter.report(CachedEntityMessage(_frontend.getVerifierName, result.method, Failure(all_cached_errors))) + _frontend.reporter report + CachedEntityMessage(_frontend.getVerifierName, result.method, Failure(cached_errors)) } }) + val methodsToVerify: Seq[Method] = transformed_prog.methods.filter(_.body.isDefined) + _frontend.logger.debug( s"Retrieved data from cache..." + s" cachedErrors: ${all_cached_errors.map(_.loggableMessage)};" + - s" methodsToVerify: ${cached_results.map(_.method.name)}.") + s" cachedMethods: ${cached_results.map(_.method.name)};" + + s" methodsToVerify: ${methodsToVerify.map(_.name)}.") _frontend.logger.trace(s"The cached program is equivalent to: \n${transformed_prog.toString()}") _frontend.setVerificationResult(_frontend.verifier.verify(transformed_prog)) _frontend.setState(DefaultStates.Verification) // update cache - val methodsToVerify = transformed_prog.methods.filter(_.body.isDefined) - methodsToVerify.foreach(m => { + methodsToVerify.foreach((m: Method) => { // Results come back irrespective of program Member. val cachable_errors = for { @@ -186,7 +191,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program } } yield cache_errs - if(cachable_errors.isDefined){ + if (cachable_errors.isDefined) { ViperCache.update(backendName, file, m, transformed_prog, cachable_errors.get) match { case e :: es => _frontend.logger.trace(s"Storing new entry in cache for method (${m.name}): $e. Other entries for this method: ($es)") diff --git a/src/main/scala/viper/server/core/ViperCache.scala b/src/main/scala/viper/server/core/ViperCache.scala index 858ada7..777a43c 100644 --- a/src/main/scala/viper/server/core/ViperCache.scala +++ b/src/main/scala/viper/server/core/ViperCache.scala @@ -291,9 +291,9 @@ object ViperCache extends Cache { object ViperCacheHelper { private val _node_hash_memo = MutableMap.empty[String, MutableMap[Node, String]] - def node_hash_memo = _node_hash_memo + def node_hash_memo: MutableMap[String, MutableMap[Node, String]] = _node_hash_memo - protected def hex(h: String) = h.hashCode.toHexString + protected def hex(h: String): String = h.hashCode.toHexString /** * This method is used for computing unique-ish hashes of AST nodes. @@ -467,14 +467,20 @@ class AccessPath(val accessPath: List[Number]) { case class ViperAst(p: Program) extends Ast { - def compose(cs: List[CacheableMember]): Ast = { - val new_methods: List[Method] = cs.map(_.asInstanceOf[ViperMethod].m) - val new_program = Program(p.domains, p.fields, p.functions, p.predicates, new_methods, p.extensions)(p.pos, p.info, p.errT) + override def compose(cs: List[CacheableMember]): Ast = { + // FIXME Use polymorphic types instead of casts! + val new_methods: List[Method] = cs filter { _.isInstanceOf[ViperMethod] } map { vm => vm.asInstanceOf[ViperMethod].m } + val new_predicates: List[Predicate] = cs filter { _.isInstanceOf[ViperPredicate] } map { vp => vp.asInstanceOf[ViperPredicate].p } + val new_functions: List[Function] = cs filter { _.isInstanceOf[ViperFunction] } map { vf => vf.asInstanceOf[ViperFunction].f } + val new_program = Program(p.domains, p.fields, + new_functions, new_predicates, new_methods, p.extensions)(p.pos, p.info, p.errT) ViperAst(new_program) } - def decompose(): List[CacheableMember] = { - p.methods.map(m => ViperMethod(m)).toList + override def decompose(): List[CacheableMember] = { + p.methods.map(m => ViperMethod(m)) ++ + p.predicates.map(p => ViperPredicate(p)) ++ + p.functions.map(f => ViperFunction(f)) toList } override def equals(other: Ast): Boolean = { @@ -505,4 +511,34 @@ case class ViperMethod(m: Method) extends CacheableMember { val p = ast.asInstanceOf[ViperAst].p p.getDependencies(p, m).map(h => ViperMember(h)) } +} + +case class ViperPredicate(p: Predicate) extends CacheableMember { + def hash(): String = { + p.entityHash + } + + def transform: CacheableMember = { + ViperPredicate(p.copy()(p.pos, ConsInfo(p.info, Cached), p.errT)) + } + + def getDependencies(ast: Ast): List[Member] = { + val p = ast.asInstanceOf[ViperAst].p + p.members filter (_.entityHash != hash()) map (h => ViperMember(h)) toList + } +} + +case class ViperFunction(f: Function) extends CacheableMember { + def hash(): String = { + f.entityHash + } + + def transform: CacheableMember = { + ViperFunction(f.copy()(f.pos, ConsInfo(f.info, Cached), f.errT)) + } + + def getDependencies(ast: Ast): List[Member] = { + val p = ast.asInstanceOf[ViperAst].p + p.members filter (_.entityHash != hash()) map (h => ViperMember(h)) toList + } } \ No newline at end of file diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index d4fa2e1..d1882a1 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -72,7 +72,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with case Some(handle_future) => val task_backend_fut = handle_future.map((handle: AstHandle[Program]) => { - val art = handle.artifact + val art: Future[Program] = handle.artifact art.map(program => { new VerificationWorker(logger.get, args :+ programId, program) }).recover({ diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 3f46603..67deb7d 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -15,7 +15,7 @@ import akka.stream.scaladsl.Source import edu.mit.csail.sdg.alloy4.A4Reporter import edu.mit.csail.sdg.parser.CompUtil import edu.mit.csail.sdg.translator.{A4Options, TranslateAlloyToKodkod} -import spray.json.DefaultJsonProtocol +import spray.json.{DefaultJsonProtocol, RootJsonFormat} import viper.server.ViperConfig import viper.server.core.{ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} @@ -149,7 +149,7 @@ class ViperHttpServer(_args: Array[String]) post { object AlloyRequest extends akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport with DefaultJsonProtocol { case class AlloyGenerationRequest(arg: String, solver: String) - implicit val generateStuff = jsonFormat2(AlloyGenerationRequest.apply) + implicit val generateStuff: RootJsonFormat[AlloyRequest.AlloyGenerationRequest] = jsonFormat2(AlloyGenerationRequest.apply) } entity(as[AlloyRequest.AlloyGenerationRequest]) { r => diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index 131a7b8..c1f3140 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -120,7 +120,7 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs }) implicit val abstractError_writer: RootJsonFormat[AbstractError] = lift(new RootJsonWriter[AbstractError] { - override def write(obj: AbstractError) = { + override def write(obj: AbstractError): JsValue = { obj match { case e: VerificationError if e.counterexample.isDefined => JsObject( @@ -130,6 +130,7 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs case src_pos: Position => src_pos.toJson case no_pos => JsString(no_pos.toString) }), + "cached" -> JsBoolean(obj.cached), "counterexample" -> e.counterexample.get.toJson) case _ => JsObject( @@ -138,7 +139,8 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs "position" -> (obj.pos match { case src_pos: Position => src_pos.toJson case no_pos => JsString(no_pos.toString) - })) + }), + "cached" -> JsBoolean(obj.cached)) } } }) From 6dec4e7830d0c3b911b1d9ad2caccc5f52dd18d3 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Sun, 29 Nov 2020 19:47:25 +0100 Subject: [PATCH 33/79] Continued refactoring VSI: - The HTTP router in VSI is backwards compatible with the old interface: the client can choose whether to submit an /ast/ast_id request or not, all the messages that were produced and not consumed yet (both from AST construction and from verification) will still be streamed via the /verify/ver_id request. - Fixed a bug with double execution of cleanup tasks after AST construction jobs - Terminator actor is no longer responsible for cleaning up after individual jobs. The JobPool and its client take care of that at the point of job creation, not at the point of streaming. - JobHandles are now linked. Added new field `prev_job_id` to VerHandle. - Important changes in initializeProcess (cleanup after queue is completed, only create job actors if the task can be executed, recover from the case of AST errors in verification jobs) - getInterruptFutureList now also cleans up AST construction tasks. --- .../scala/viper/server/core/AstWorker.scala | 2 +- .../viper/server/core/ViperCoreServer.scala | 4 +- .../server/core/ViperCoreServerUtils.scala | 2 +- .../frontends/http/ViperHttpServer.scala | 7 +- src/main/scala/viper/server/vsi/HTTP.scala | 48 +++++++--- .../scala/viper/server/vsi/JobActor.scala | 6 +- src/main/scala/viper/server/vsi/JobPool.scala | 10 ++- .../scala/viper/server/vsi/Protocol.scala | 9 +- .../scala/viper/server/vsi/Terminator.scala | 19 ---- .../viper/server/vsi/VerificationServer.scala | 87 ++++++++++++------- 10 files changed, 118 insertions(+), 76 deletions(-) diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala index 9fc72ed..4341196 100644 --- a/src/main/scala/viper/server/core/AstWorker.scala +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -4,11 +4,11 @@ package viper.server.core import ch.qos.logback.classic.Logger import viper.server.utility.AstGenerator import viper.server.utility.Helpers.getArgListFromArgString +import viper.server.vsi.AstConstructionException import viper.silver.ast.Program import viper.silver.reporter.ExceptionReport -abstract class AstConstructionException extends Exception object ViperFileNotFoundException extends AstConstructionException object AstConstructionInterrupted extends AstConstructionException object InvalidArgumentsException extends AstConstructionException diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index d1882a1..0fc2384 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -83,7 +83,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with }).flatten - initializeVerificationProcess(task_backend_fut) + initializeVerificationProcess(task_backend_fut, Some(ast_id)) case None => logger.get.error(s"Could not start verification process for non-existent $ast_id") @@ -100,7 +100,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with val args: List[String] = backend_config.toList val task_backend = new VerificationWorker(logger.get, args :+ programId, program) - val ver_id = initializeVerificationProcess(Future.successful(task_backend)) + val ver_id = initializeVerificationProcess(Future.successful(task_backend), None) if (ver_id.id >= 0) { logger.get.info(s"Verification process #${ver_id.id} has successfully started.") diff --git a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala index 4e37f92..f8f0083 100644 --- a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala +++ b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala @@ -47,7 +47,7 @@ object ViperCoreServerUtils { import scala.language.postfixOps val actor = actor_system.actorOf(SeqActor.props()) - val complete_future = core.streamMessages(jid, actor).getOrElse(return Future.failed(JobNotFoundException())) + val complete_future = core.streamMessages(jid, actor).getOrElse(return Future.failed(JobNotFoundException)) val res: Future[List[Message]] = complete_future.flatMap(_ => { implicit val askTimeout: Timeout = Timeout(core.config.actorCommunicationTimeout() milliseconds) val answer: Future[Any] = actor ? SeqActor.Result diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 67deb7d..e5298a7 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -17,7 +17,7 @@ import edu.mit.csail.sdg.parser.CompUtil import edu.mit.csail.sdg.translator.{A4Options, TranslateAlloyToKodkod} import spray.json.{DefaultJsonProtocol, RootJsonFormat} import viper.server.ViperConfig -import viper.server.core.{ViperBackendConfig, ViperCache, ViperCoreServer} +import viper.server.core.{AstConstructionFailureException, ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} import viper.server.utility.Helpers.getArgListFromArgString import viper.server.vsi.Requests.CacheResetRequest @@ -87,9 +87,12 @@ class ViperHttpServer(_args: Array[String]) override def verificationRequestRejection(jid: Int, e: Throwable): ToResponseMarshallable = { e match { - case JobNotFoundException() => + case JobNotFoundException => logger.get.error(s"The verification job #$jid does not exist.") VerificationRequestReject(s"The verification job #$jid does not exist.") + case AstConstructionFailureException => + logger.get.error(s"The verification job #$jid could not be created since the AST is inconsistent.") + VerificationRequestReject(s"The verification job #$jid could not be created since the AST is inconsistent.") case _ => logger.get.error(s"The verification job #$jid resulted in a terrible error: $e") VerificationRequestReject(s"The verification job #$jid resulted in a terrible error: $e") diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index 379be39..c0c8366 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -122,7 +122,6 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { onComplete(handle_future) { case Success(handle) => val s: Source[Envelope, NotUsed] = Source.fromPublisher(handle.publisher) - _termActor ! Terminator.WatchJobQueue(ast_id, handle) complete(unpackMessages(s)) case Failure(error) => // TODO use AST-specific response @@ -130,10 +129,9 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { } case None => // TODO use AST-specific response - complete(verificationRequestRejection(id, JobNotFoundException())) + complete(verificationRequestRejection(id, JobNotFoundException)) } } - } ~ path("verify" / IntNumber) { id => /** Send GET request to "/verify/" where is a non-negative integer. * must be an ID of an existing verification job. @@ -141,18 +139,46 @@ trait VerificationServerHttp extends VerificationServer with CustomizableHttp { val ver_id = VerJobId(id) get { ver_jobs.lookupJob(ver_id) match { + case None => + /** Verification job with this ID is not found. */ + complete(verificationRequestRejection(id, JobNotFoundException)) + case Some(handle_future) => - // Found a job with this jid. - onComplete(handle_future) { - case Success(handle) => - val s: Source[Envelope, NotUsed] = Source.fromPublisher(handle.publisher) - _termActor ! Terminator.WatchJobQueue(ver_id, handle) - complete(unpackMessages(s)) + /** Combine the future AST and the future verification results. */ + onComplete(handle_future.flatMap((ver_handle: VerHandle) => { + /** If there exists a verification job, there should have existed + * (or should still exist) a corresponding AST construction job. */ + val ast_id: AstJobId = ver_handle.prev_job_id.get + + /** The AST construction job may have been cleaned up + * (if all of its messages were already consumed) */ + ast_jobs.lookupJob(ast_id) match { + case Some(ast_handle_fut) => + ast_handle_fut.map(ast_handle => (Some(ast_handle), ver_handle)) + case None => + Future.successful((None, ver_handle)) + } + })) { + case Success((ast_handle_maybe, ver_handle)) => + val ver_source = ver_handle match { + case VerHandle(null, null, null, ast_id) => + /** There were no messages produced during verification. */ + Source.empty[Envelope] + case _ => + Source.fromPublisher(ver_handle.publisher) + } + val ast_source = ast_handle_maybe match { + case None => + /** The AST messages were already consumed. */ + Source.empty[Envelope] + case Some(ast_handle) => + Source.fromPublisher(ast_handle.publisher) + } + val resulting_source = ver_source.prepend(ast_source) + complete(unpackMessages(resulting_source)) case Failure(error) => complete(verificationRequestRejection(id, error)) } - case None => - complete(verificationRequestRejection(id, JobNotFoundException())) } } } ~ path("discard" / IntNumber) { id => diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index 3e81471..2443376 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -60,12 +60,12 @@ class JobActor[T](private val id: JobId) extends Actor { _astConstructionTask.start() sender ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact) - case _: Verify[T] => + case ver_req: Verify[T] => println(">>> JobActor received request Verify") resetVerificationTask() - _verificationTask = req.task + _verificationTask = ver_req.task _verificationTask.start() - sender ! VerHandle(self, req.queue, req.publisher) + sender ! VerHandle(self, ver_req.queue, ver_req.publisher, ver_req.prev_job_id) } case req: StopProcessRequest => val did_I_interrupt = req match { diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index 8dc405e..af7ae27 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -37,7 +37,8 @@ case class AstHandle[R](job_actor: ActorRef, case class VerHandle(job_actor: ActorRef, queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) extends JobHandle { + publisher: Publisher[Envelope], + prev_job_id: Option[AstJobId]) extends JobHandle { def tag = "VER" } @@ -65,7 +66,10 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: /** This prevents recomputing the same future multiple times. */ _jobCache(new_jid) } else { + /** Create Future[JobHandle]. */ val t_fut = job_executor(new_jid) + + /** Cache the newly created Future[JobHandle] for later reference. */ _jobCache(new_jid) = t_fut t_fut } @@ -83,10 +87,10 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: def lookupJob(jid: S): Option[Future[T]] = { _jobHandles.get(jid).map((promise: Promise[T]) => { + /** 1. Execute the job and get the result's future OR get the priorly cached result's future. + * 2. Complete the promise with this future. */ promise.completeWith(_jobExecutors(jid)()) - //promise.future.map(_.queue.watchCompletion().onComplete(_ => discardJob(jid))) - promise.future }) } diff --git a/src/main/scala/viper/server/vsi/Protocol.scala b/src/main/scala/viper/server/vsi/Protocol.scala index 3374a3a..ba9efce 100644 --- a/src/main/scala/viper/server/vsi/Protocol.scala +++ b/src/main/scala/viper/server/vsi/Protocol.scala @@ -26,13 +26,14 @@ object VerificationProtocol { // Request Job Actor to execute an AST construction task case class ConstructAst[T](task: TaskThread[T], - queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) extends StartProcessRequest[T] + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope]) extends StartProcessRequest[T] // Request Job Actor to execute a verification task case class Verify[T](task: TaskThread[T], - queue: SourceQueueWithComplete[Envelope], - publisher: Publisher[Envelope]) extends StartProcessRequest[T] + queue: SourceQueueWithComplete[Envelope], + publisher: Publisher[Envelope], + prev_job_id: Option[AstJobId]) extends StartProcessRequest[T] sealed trait StopProcessRequest diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala index 4a949f8..2f213de 100644 --- a/src/main/scala/viper/server/vsi/Terminator.scala +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -1,9 +1,7 @@ package viper.server.vsi -import akka.Done import akka.actor.{Actor, ActorSystem, Props} import akka.http.scaladsl.Http -import scala.util.{Failure, Success} import scala.concurrent.{ExecutionContext, Future} @@ -39,22 +37,5 @@ class Terminator[R](ast_jobs: JobPool[AstJobId, AstHandle[R]], case None => sys.terminate() // shutdown } - case Terminator.WatchJobQueue(jid, handle) => - val queue_completion_future: Future[Done] = handle.queue.watchCompletion() - queue_completion_future.onComplete { - case Failure(e) => - throw e - case Success(_) => - jid match { - case ast_id: AstJobId => - // TODO: Use logger - //println(s"Terminator: Discarding AST construction job $ast_id") - ast_jobs.discardJob(ast_id) - case ver_id: VerJobId => - // TODO: Use logger - //println(s"Terminator: Discarding verification job $ver_id") - ver_jobs.discardJob(ver_id) - } - } } } \ No newline at end of file diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 99e865f..1693562 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -7,7 +7,7 @@ package viper.server.vsi import akka.NotUsed -import akka.actor.{ActorRef, ActorSystem} +import akka.actor.{ActorRef, ActorSystem, PoisonPill} import akka.pattern.ask import akka.stream.scaladsl.{Keep, Sink, Source} import akka.stream.{ActorMaterializer, OverflowStrategy} @@ -19,8 +19,9 @@ import scala.reflect.ClassTag import scala.util.{Failure, Success} -class VerificationServerException extends Exception -case class JobNotFoundException() extends VerificationServerException +abstract class VerificationServerException extends Exception +case object JobNotFoundException extends VerificationServerException +abstract class AstConstructionException extends VerificationServerException /** This trait provides state and process management functionality for verification servers. * @@ -70,7 +71,7 @@ trait VerificationServer extends Post { protected def initializeProcess[S <: JobId, T <: JobHandle : ClassTag] (pool: JobPool[S, T], task_fut: Future[MessageStreamingTask[AST]], - actor_fut_maybe: Option[Future[ActorRef]] = None): S = { + prev_job_id_maybe: Option[AstJobId] = None): S = { if (!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") @@ -80,7 +81,7 @@ trait VerificationServer extends Post { /** Ask the pool to book a new job using the above function * to construct Future[JobHandle] and Promise[AST] later on. */ - pool.bookNewJob((new_jid: JobId) => { + pool.bookNewJob((new_jid: S) => task_fut.flatMap((task: MessageStreamingTask[AST]) => { /** TODO avoid hardcoded parameters */ implicit val askTimeout: Timeout = Timeout(5000 milliseconds) @@ -100,20 +101,44 @@ trait VerificationServer extends Post { * '---------- Task II ----------' **/ val message_actor = system.actorOf(QueueActor.props(queue), s"${pool.tag}--message_actor--${new_jid.id}") - - task_fut.map(task => { - task.setQueueActor(message_actor) - - val job_actor = system.actorOf(JobActor.props(new_jid), s"${pool.tag}_job_actor_${new_jid}") - - (job_actor ? (new_jid match { - case _: AstJobId => - VerificationProtocol.ConstructAst(new TaskThread(task), queue, publisher) + task.setQueueActor(message_actor) + + val job_actor = system.actorOf(JobActor.props(new_jid), s"${pool.tag}_job_actor_${new_jid}") + + /** Register cleanup task. */ + queue.watchCompletion().onComplete(_ => { + pool.discardJob(new_jid) + /** FIXME: if the job actors are meant to be reused from one phase to another (not implemented yet), + * FIXME: then they should be stopped only after the **last** job is completed in the pipeline. */ + job_actor ! PoisonPill + }) + + (job_actor ? (new_jid match { + case _: AstJobId => + VerificationProtocol.ConstructAst(new TaskThread(task), queue, publisher) + case _: VerJobId => + VerificationProtocol.Verify(new TaskThread(task), queue, publisher, + /** TODO: Use factories for specializing the messages. + * TODO: Clearly, there should be a clean separation between concrete job types + * TODO: (AST Construction, Verification) and generic types (JobHandle). */ + prev_job_id_maybe match { + case Some(prev_job_id: AstJobId) => + Some(prev_job_id) + case Some(prev_job_id) => + throw new IllegalArgumentException(s"cannot map ${prev_job_id.toString} to expected type AstJobId") + case None => + None + }) + })).mapTo[T] + + }).recover({ + case e: AstConstructionException => + pool.discardJob(new_jid) + new_jid match { case _: VerJobId => - VerificationProtocol.Verify(new TaskThread(task), queue, publisher) - })).mapTo[T] - }).flatten - }) + VerHandle(null, null, null, prev_job_id_maybe) + } + }).mapTo[T]) } protected def initializeAstConstruction(task: MessageStreamingTask[AST]): AstJobId = { @@ -122,7 +147,7 @@ trait VerificationServer extends Post { } if (ast_jobs.newJobsAllowed) { - initializeProcess(ast_jobs, Future.successful(task), None) + initializeProcess(ast_jobs, Future.successful(task)) } else { AstJobId(-1) // Process Management running at max capacity. } @@ -132,15 +157,15 @@ trait VerificationServer extends Post { * * As such, it accepts an instance of a VerificationTask, which it will pass to the JobActor. */ - protected def initializeVerificationProcess(task_fut: Future[MessageStreamingTask[AST]]): VerJobId = { + protected def initializeVerificationProcess(task_fut: Future[MessageStreamingTask[AST]], ast_job_id_maybe: Option[AstJobId]): VerJobId = { if (!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") } if (ver_jobs.newJobsAllowed) { - initializeProcess(ver_jobs, task_fut, None) + initializeProcess(ver_jobs, task_fut, ast_job_id_maybe) } else { - VerJobId(-1) // Process Management running at max capacity. + VerJobId(-1) // Process Management running at max capacity. } } @@ -159,7 +184,6 @@ trait VerificationServer extends Post { val src_envelope: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) val src_msg: Source[A , NotUsed] = src_envelope.map(e => unpack(e)) src_msg.runWith(Sink.actorRef(clientActor, Success)) - _termActor ! Terminator.WatchJobQueue(jid, handle) handle.queue.watchCompletion().map(_ => ()) } Some(handle_future.flatMap(mapHandle)) @@ -191,13 +215,16 @@ trait VerificationServer extends Post { /** This method interrupts active jobs upon termination of the server. */ protected def getInterruptFutureList(): Future[List[String]] = { - val interrupt_future_list: List[Future[String]] = ver_jobs.jobHandles map { case (jid, handle_future) => - handle_future.flatMap { - case VerHandle(actor, _, _) => - implicit val askTimeout: Timeout = Timeout(1000 milliseconds) - (actor ? VerificationProtocol.StopVerification).mapTo[String] - } - } toList + implicit val askTimeout: Timeout = Timeout(1000 milliseconds) + val interrupt_future_list: List[Future[String]] = (ver_jobs.jobHandles ++ ast_jobs.jobHandles) map { + case (jid, handle_future) => + handle_future.flatMap { + case AstHandle(actor, _, _, _) => + (actor ? VerificationProtocol.StopAstConstruction).mapTo[String] + case VerHandle(actor, _, _, _) => + (actor ? VerificationProtocol.StopVerification).mapTo[String] + } + } toList val overall_interrupt_future: Future[List[String]] = Future.sequence(interrupt_future_list) overall_interrupt_future } From 96f40c17566b45c617d81c4bf065a7fe391d6fe7 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Fri, 11 Dec 2020 18:05:47 +0100 Subject: [PATCH 34/79] Solve compilation problems after updating SBT to 1.4.4 and Scala to 2.13.4 --- build.sbt | 21 +++++++------------ project/build.properties | 2 +- project/plugins.sbt | 4 ++-- .../scala/viper/server/core/AstWorker.scala | 4 ++-- .../server/core/MessageReportingTask.scala | 2 +- .../server/core/VerificationWorker.scala | 2 +- .../scala/viper/server/core/ViperCache.scala | 2 ++ .../frontends/lsp/ViperServerService.scala | 2 ++ .../utility/ProgramDefinitionsProvider.scala | 4 +++- .../scala/viper/server/vsi/JobActor.scala | 4 ++-- .../server/vsi/MessageStreamingTask.scala | 2 ++ .../viper/server/vsi/VerificationServer.scala | 2 ++ 12 files changed, 28 insertions(+), 23 deletions(-) diff --git a/build.sbt b/build.sbt index 8fafdbd..ec3e47e 100644 --- a/build.sbt +++ b/build.sbt @@ -11,15 +11,6 @@ lazy val carbon = project in file("carbon") lazy val common = (project in file("common")) -// Publishing settings -ThisBuild / Test / publishArtifact := true -// Allows 'publishLocal' SBT command to include test artifacts in a dedicated JAR file -// (whose name is postfixed by 'test-source') and publish it in the local Ivy repository. -// This JAR file contains all classes and resources for testing and projects like Carbon -// and Silicon can rely on it to access the test suit implemented in Silver. - -ThisBuild / Test / parallelExecution := false - // Viper Server specific project settings lazy val server = (project in file(".")) .dependsOn(silver % "compile->compile;test->test") @@ -34,11 +25,15 @@ lazy val server = (project in file(".")) organization := "viper", version := "1.1-SNAPSHOT", + // Fork test to a different JVM than SBT's, avoiding SBT's classpath interfering with + // classpath used by Scala's reflection. + Test / fork := true, + // Compilation settings - libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.5.22", + libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.5.23", libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8", - libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.22", - libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % "2.5.22" % Test, + libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23", + libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % "2.5.23" % Test, libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % "10.1.8" % Test, libraryDependencies += "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.8.1", // Java implementation of language server protocol @@ -46,7 +41,7 @@ lazy val server = (project in file(".")) run / javaOptions += "-Xss128m", // Test settings - fork := true, + Test / parallelExecution := false, // Assembly settings assembly / assemblyJarName := "viper.jar", // JAR filename diff --git a/project/build.properties b/project/build.properties index 15e1475..42704b7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,4 +1,4 @@ # Any copyright is dedicated to the Public Domain. # http://creativecommons.org/publicdomain/zero/1.0/ -sbt.version=1.2.6 +sbt.version=1.4.4 diff --git a/project/plugins.sbt b/project/plugins.sbt index cb1acf1..7d66638 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ // Any copyright is dedicated to the Public Domain. // http://creativecommons.org/publicdomain/zero/1.0/ -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.8") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.12") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala index 4341196..8c76813 100644 --- a/src/main/scala/viper/server/core/AstWorker.scala +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -29,7 +29,7 @@ class AstWorker(val input: String, private def constructAst(): Future[Program] = Future { - println(">>> AstWorker.constructAst()") + //println(">>> AstWorker.constructAst()") val arg_list = getArgListFromArgString(input) val file: String = arg_list.last @@ -72,7 +72,7 @@ class AstWorker(val input: String, } override def run(): Unit = { - println(">>> AstWorker.run()") + //println(">>> AstWorker.run()") _artifact_pro.completeWith(constructAst()) } diff --git a/src/main/scala/viper/server/core/MessageReportingTask.scala b/src/main/scala/viper/server/core/MessageReportingTask.scala index 5236f08..4367109 100644 --- a/src/main/scala/viper/server/core/MessageReportingTask.scala +++ b/src/main/scala/viper/server/core/MessageReportingTask.scala @@ -17,7 +17,7 @@ trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost val name = s"ViperServer_$tag" def report(msg: Message): Unit = { - println(s">>> ActorReporter.report($msg)") + //println(s">>> ActorReporter.report($msg)") msg match { case m: EntityFailureMessage if m.concerning.info.isCached => case m: EntitySuccessMessage if m.concerning.info.isCached => diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index d5f39d6..877e197 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -207,7 +207,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program case Failure(errorList) => _frontend.setVerificationResult(Failure(errorList ++ all_cached_errors)) case Success => - _frontend.setVerificationResult(Failure(all_cached_errors)) + _frontend.setVerificationResult(Failure(all_cached_errors.toSeq)) } } } diff --git a/src/main/scala/viper/server/core/ViperCache.scala b/src/main/scala/viper/server/core/ViperCache.scala index 777a43c..e2d8f57 100644 --- a/src/main/scala/viper/server/core/ViperCache.scala +++ b/src/main/scala/viper/server/core/ViperCache.scala @@ -6,6 +6,8 @@ package viper.server.core +import scala.language.postfixOps + import ch.qos.logback.classic.Logger import viper.server.core.ViperCache.logger import viper.server.vsi._ diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 8b18237..d06c0bd 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -8,6 +8,8 @@ package viper.server.frontends.lsp +import scala.language.postfixOps + import java.util.concurrent.{CompletableFuture => CFuture} import akka.actor.{PoisonPill, Props} diff --git a/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala b/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala index efa425c..7462491 100644 --- a/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala +++ b/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala @@ -1,5 +1,7 @@ package viper.server.utility +import scala.language.postfixOps + import viper.silver.ast.{AbstractSourcePosition, Domain, Field, Function, LocalVarDecl, Method, NamedDomainAxiom, Positioned, Predicate, Program, Scope} import viper.silver.frontend.SilFrontend import viper.silver.reporter.{Definition, ProgramDefinitionsReport, ProgramOutlineReport, StatisticsReport} @@ -98,7 +100,7 @@ trait ProgramDefinitionsProvider { case d: Domain => "domain" case fi: Field => "field" case _ => "other" - }).mapValues(_.size) + }).mapValues(_.size).toMap def reportProgramStats(): Unit = { val prog = _frontend.program.get diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index 2443376..048a0f0 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -54,14 +54,14 @@ class JobActor[T](private val id: JobId) extends Actor { case req: StartProcessRequest[T] => req match { case _: ConstructAst[T] => - println(">>> JobActor received request ConstructAst") + //println(">>> JobActor received request ConstructAst") resetAstConstructionTask() _astConstructionTask = req.task _astConstructionTask.start() sender ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact) case ver_req: Verify[T] => - println(">>> JobActor received request Verify") + //println(">>> JobActor received request Verify") resetVerificationTask() _verificationTask = ver_req.task _verificationTask.start() diff --git a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala index 7b237ec..b0adf28 100644 --- a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -1,5 +1,7 @@ package viper.server.vsi +import scala.language.postfixOps + import akka.actor.ActorRef import akka.pattern.ask import akka.stream.QueueOfferResult diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 1693562..8576d0f 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -6,6 +6,8 @@ package viper.server.vsi +import scala.language.postfixOps + import akka.NotUsed import akka.actor.{ActorRef, ActorSystem, PoisonPill} import akka.pattern.ask From 0ac4be79c7c91cfab2a5f988bc21dccd7de96f70 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Fri, 11 Dec 2020 21:04:29 +0100 Subject: [PATCH 35/79] Ensure backwards compatibility. --- .../server/frontends/http/jsonWriters/ViperIDEProtocol.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index c1f3140..4c5e37b 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -30,7 +30,7 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs implicit val verReqAccept_writer: RootJsonFormat[VerificationRequestAccept] = lift(new RootJsonWriter[VerificationRequestAccept] { override def write(obj: VerificationRequestAccept): JsValue = JsObject( "ast_id" -> JsNumber(obj.ast_id.id), - "ver_id" -> JsNumber(obj.ver_id.id) + "id" -> JsNumber(obj.ver_id.id) ) }) implicit val verReqReject_format: RootJsonFormat[VerificationRequestReject] = jsonFormat1(VerificationRequestReject.apply) From b063c6534ab60568affccd126d1509dcf89f4c91 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Sat, 19 Dec 2020 19:59:19 +0100 Subject: [PATCH 36/79] Fixed a bug causing AstConstructor to crash while trying to verify non-existent files. --- src/main/scala/viper/server/ViperConfig.scala | 17 +++----------- .../scala/viper/server/core/AstWorker.scala | 5 +---- .../viper/server/core/ViperCoreServer.scala | 4 ++-- .../frontends/http/ViperHttpServer.scala | 11 +++++++--- .../scala/viper/server/utility/Helpers.scala | 22 +++++++++++++++++++ .../viper/server/vsi/VerificationServer.scala | 4 ++-- src/test/scala/ViperServerTests.scala | 2 +- 7 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/main/scala/viper/server/ViperConfig.scala b/src/main/scala/viper/server/ViperConfig.scala index 0eeef59..d05e03d 100644 --- a/src/main/scala/viper/server/ViperConfig.scala +++ b/src/main/scala/viper/server/ViperConfig.scala @@ -9,6 +9,7 @@ package viper.server import java.io.File import org.rogach.scallop.{ScallopConf, ScallopOption, singleArgConverter} +import viper.server.utility.Helpers.{canonizedFile, validatePath} import viper.server.utility.ibm import viper.server.utility.ibm.Socket @@ -31,24 +32,12 @@ class ViperConfig(args: Seq[String]) extends ScallopConf(args) { val temp: File = java.io.File.createTempFile("viperserver_journal_" + System.currentTimeMillis(), ".log") Some(temp.getAbsolutePath) }, - validate = (path: String) => { - val f = canonizedLogFile(path) - (f.isFile || f.isDirectory) && f.canWrite || f.getParentFile.canWrite - }, + validate = validatePath, noshort = true, hidden = false) - private def canonizedLogFile(some_file_path: String): File = { - val f = new File(some_file_path) - if (f.isAbsolute) { - f - } else { - java.nio.file.Paths.get(System.getProperty("user.dir"), some_file_path).toFile - } - } - def getLogFileWithGuarantee: String = { - val cf: File = canonizedLogFile(logFile()) + val cf: File = canonizedFile(logFile()) if ( cf.isDirectory ) { val log: File = java.io.File.createTempFile("viperserver_journal_" + System.currentTimeMillis(), ".log", cf) log.toString diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala index 4341196..f468ec3 100644 --- a/src/main/scala/viper/server/core/AstWorker.scala +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -3,7 +3,6 @@ package viper.server.core import ch.qos.logback.classic.Logger import viper.server.utility.AstGenerator -import viper.server.utility.Helpers.getArgListFromArgString import viper.server.vsi.AstConstructionException import viper.silver.ast.Program import viper.silver.reporter.ExceptionReport @@ -20,10 +19,9 @@ case class ServerCrashException(e: Throwable) extends Exception(e) import scala.concurrent.{ExecutionContext, Future, Promise} -class AstWorker(val input: String, +class AstWorker(val arg_list: List[String], val logger: Logger)(implicit val ec: ExecutionContext) extends MessageReportingTask { - // private var _ast: Promise[Program] = private val _artifact_pro: Promise[Program] = Promise() override def artifact: Future[Program] = _artifact_pro.future @@ -31,7 +29,6 @@ class AstWorker(val input: String, println(">>> AstWorker.constructAst()") - val arg_list = getArgListFromArgString(input) val file: String = arg_list.last val reporter = new ActorReporter("AstGenerationReporter") diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 0fc2384..eca01ff 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -45,10 +45,10 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with println(s"ViperCoreServer started.") } - def requestAst(input: String): AstJobId = { + def requestAst(arg_list: List[String]): AstJobId = { require(config != null) - val task_backend = new AstWorker(input, logger.get) + val task_backend = new AstWorker(arg_list, logger.get) val ast_id = initializeAstConstruction(task_backend) if (ast_id.id >= 0) { diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index e5298a7..a0dafd8 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -19,7 +19,7 @@ import spray.json.{DefaultJsonProtocol, RootJsonFormat} import viper.server.ViperConfig import viper.server.core.{AstConstructionFailureException, ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} -import viper.server.utility.Helpers.getArgListFromArgString +import viper.server.utility.Helpers.{getArgListFromArgString, validateViperFile} import viper.server.vsi.Requests.CacheResetRequest import viper.server.vsi._ import viper.silver.logger.ViperLogger @@ -61,9 +61,14 @@ class ViperHttpServer(_args: Array[String]) } override def onVerifyPost(vr: Requests.VerificationRequest): ToResponseMarshallable = { - val ast_id = requestAst(vr.arg) - val arg_list = getArgListFromArgString(vr.arg) + + if (!validateViperFile(arg_list.last)) { + return VerificationRequestReject("File not found") + } + + val ast_id = requestAst(arg_list) + val arg_list_partial: List[String] = arg_list.dropRight(1) val backend = try { ViperBackendConfig(arg_list_partial) diff --git a/src/main/scala/viper/server/utility/Helpers.scala b/src/main/scala/viper/server/utility/Helpers.scala index e02a63a..5478a02 100644 --- a/src/main/scala/viper/server/utility/Helpers.scala +++ b/src/main/scala/viper/server/utility/Helpers.scala @@ -1,5 +1,7 @@ package viper.server.utility +import java.io.File + object Helpers { def getArgListFromArgString(arg_str: String): List[String] = { val possibly_quoted_string = raw"""[^\s"']+|"[^"]*"|'[^']*'""".r @@ -9,4 +11,24 @@ object Helpers { case a => a } } + + def canonizedFile(some_file_path: String): File = { + val f = new File(some_file_path) + if (f.isAbsolute) { + f + } else { + java.nio.file.Paths.get(System.getProperty("user.dir"), some_file_path).toFile + } + } + + def validatePath(path: String): Boolean = { + val f = canonizedFile(path) + (f.isFile || f.isDirectory) && f.canWrite || f.getParentFile.canWrite + } + + def validateViperFile(path: String): Boolean = { + val f = canonizedFile(path) + f.isFile && f.canWrite + } + } diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index 1693562..a297a36 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -62,8 +62,8 @@ trait VerificationServer extends Post { * will result in an IllegalStateException. * */ def start(active_jobs: Int): Unit = { - ast_jobs = new JobPool("Http-AST-pool", active_jobs) - ver_jobs = new JobPool("Http-Verification-pool", active_jobs) + ast_jobs = new JobPool("VSI-AST-pool", active_jobs) + ver_jobs = new JobPool("VSI-Verification-pool", active_jobs) _termActor = system.actorOf(Terminator.props(ast_jobs, ver_jobs), "terminator") isRunning = true } diff --git a/src/test/scala/ViperServerTests.scala b/src/test/scala/ViperServerTests.scala index 83f4801..3f96ce5 100644 --- a/src/test/scala/ViperServerTests.scala +++ b/src/test/scala/ViperServerTests.scala @@ -91,7 +91,7 @@ class ViperServerSpec extends WordSpec with Matchers with ScalatestRouteTest { } } - s"start another verification process using `$tool` on an inexistent file" in { + s"start another verification process using `$tool` on an non-existent file" in { Post("/verify", VerificationRequest(testNonExistingFile_cmd)) ~> _routsUnderTest ~> check { //printRequestResponsePair(s"POST, /verify, $testEmptyFile_cmd", responseAs[String]) responseAs[String] should include (s"not found") From a729b64b9c76c7a4cd03450225ba10fc7a857a58 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 23 Dec 2020 02:43:39 +0100 Subject: [PATCH 37/79] - adapted streamMessages after refactoring the job pipelines - check for non-existent files in generateViperAst - streamMessages now returns Option[Future[Done]] rather than Option[Future[Unit]] --- .../viper/server/core/ViperCoreServer.scala | 3 +- .../server/core/ViperCoreServerUtils.scala | 6 +- .../frontends/http/ViperHttpServer.scala | 2 +- .../viper/server/utility/AstGenerator.scala | 8 +++ src/main/scala/viper/server/vsi/JobPool.scala | 2 - .../viper/server/vsi/VerificationServer.scala | 59 +++++++++++++++---- 6 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index eca01ff..1b79376 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -6,6 +6,7 @@ package viper.server.core +import akka.Done import akka.actor.ActorRef import viper.server.ViperConfig import viper.server.vsi.{AstHandle, AstJobId, VerJobId, VerificationServer} @@ -111,7 +112,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with ver_id } - override def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Unit]] = { + override def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Done]] = { logger.get.info(s"Streaming results for job #${jid.id}.") super.streamMessages(jid, clientActor) } diff --git a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala index f8f0083..ea24051 100644 --- a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala +++ b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala @@ -9,15 +9,15 @@ package viper.server.core import akka.actor.{Actor, ActorSystem, Props} import akka.pattern.ask import akka.util.Timeout -import viper.server.vsi.{VerJobId, JobNotFoundException} +import viper.server.vsi.{JobNotFoundException, VerJobId} import viper.silver.reporter.{EntityFailureMessage, Message} import viper.silver.verifier.{AbstractError, VerificationResult, Failure => VerificationFailure, Success => VerificationSuccess} import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} object ViperCoreServerUtils { - implicit private val executionContext = ExecutionContext.global + implicit private val executionContext: ExecutionContextExecutor = ExecutionContext.global private object SeqActor { case object Result diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index a0dafd8..4269674 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -195,4 +195,4 @@ class ViperHttpServer(_args: Array[String]) } } } -} \ No newline at end of file +} diff --git a/src/main/scala/viper/server/utility/AstGenerator.scala b/src/main/scala/viper/server/utility/AstGenerator.scala index 4113988..4f85be6 100644 --- a/src/main/scala/viper/server/utility/AstGenerator.scala +++ b/src/main/scala/viper/server/utility/AstGenerator.scala @@ -6,7 +6,10 @@ package viper.server.utility +import java.nio.file.NoSuchFileException + import ch.qos.logback.classic.Logger +import viper.server.utility.Helpers.validateViperFile import viper.silver.ast.Program import viper.silver.frontend.{SilFrontend, ViperAstProvider} import viper.silver.reporter.{NoopReporter, Reporter} @@ -25,6 +28,11 @@ class AstGenerator(private val _logger: Logger, * Throws an exception when passed an non-existent file! */ def generateViperAst(vpr_file_path: String): Option[Program] = { + + if (!validateViperFile(vpr_file_path)) { + throw new NoSuchFileException(vpr_file_path) + } + val args: Array[String] = Array(vpr_file_path) _logger.info(s"Parsing Viper file ...") diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index af7ae27..224a9bd 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -74,7 +74,6 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: t_fut } } - _nextJobId = _nextJobId + 1 new_jid } @@ -90,7 +89,6 @@ class JobPool[S <: JobId, T <: JobHandle](val tag: String, val MAX_ACTIVE_JOBS: /** 1. Execute the job and get the result's future OR get the priorly cached result's future. * 2. Complete the promise with this future. */ promise.completeWith(_jobExecutors(jid)()) - promise.future }) } diff --git a/src/main/scala/viper/server/vsi/VerificationServer.scala b/src/main/scala/viper/server/vsi/VerificationServer.scala index fc3ad3f..4830329 100644 --- a/src/main/scala/viper/server/vsi/VerificationServer.scala +++ b/src/main/scala/viper/server/vsi/VerificationServer.scala @@ -6,9 +6,7 @@ package viper.server.vsi -import scala.language.postfixOps - -import akka.NotUsed +import akka.Done import akka.actor.{ActorRef, ActorSystem, PoisonPill} import akka.pattern.ask import akka.stream.scaladsl.{Keep, Sink, Source} @@ -17,6 +15,7 @@ import akka.util.Timeout import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} +import scala.language.postfixOps import scala.reflect.ClassTag import scala.util.{Failure, Success} @@ -135,9 +134,12 @@ trait VerificationServer extends Post { }).recover({ case e: AstConstructionException => + // If the AST construction phase failed, remove the verification job handle + // from the corresponding pool. pool.discardJob(new_jid) new_jid match { case _: VerJobId => + // FIXME perhaps return None instead of nulls here. VerHandle(null, null, null, prev_job_id_maybe) } }).mapTo[T]) @@ -175,21 +177,54 @@ trait VerificationServer extends Post { * * Deletes the JobHandle on completion. */ - protected def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Unit]] = { + protected def streamMessages(jid: VerJobId, clientActor: ActorRef): Option[Future[Done]] = { if (!isRunning) { throw new IllegalStateException("Instance of VerificationServer already stopped") } ver_jobs.lookupJob(jid) match { + case None => + /** Verification job not found */ + None case Some(handle_future) => - def mapHandle(handle: VerHandle): Future[Unit] = { - val src_envelope: Source[Envelope, NotUsed] = Source.fromPublisher((handle.publisher)) - val src_msg: Source[A , NotUsed] = src_envelope.map(e => unpack(e)) - src_msg.runWith(Sink.actorRef(clientActor, Success)) - handle.queue.watchCompletion().map(_ => ()) - } - Some(handle_future.flatMap(mapHandle)) - case None => None + Some(handle_future.flatMap((ver_handle: VerHandle) => { + ver_handle.prev_job_id match { + case None => + /** The AST for this verification job wasn't created by this server. */ + Future.successful((None, ver_handle)) + case Some(ast_id) => + /** The AST construction job may have been cleaned up + * (if all of its messages were already consumed) */ + ast_jobs.lookupJob(ast_id) match { + case Some(ast_handle_fut) => + ast_handle_fut.map(ast_handle => (Some(ast_handle), ver_handle)) + case None => + Future.successful((None, ver_handle)) + } + } + }) flatMap { + case (ast_handle_maybe: Option[AstHandle[AST]], ver_handle: VerHandle) => + val ver_source = ver_handle match { + case VerHandle(null, null, null, ast_id) => + /** There were no messages produced during verification. */ + Source.empty[Envelope] + case _ => + Source.fromPublisher(ver_handle.publisher) + } + val ast_source = ast_handle_maybe match { + case None => + /** The AST messages were already consumed. */ + Source.empty[Envelope] + case Some(ast_handle) => + Source.fromPublisher(ast_handle.publisher) + } + val resulting_source = ver_source.prepend(ast_source).map(e => unpack(e)) + resulting_source.runWith(Sink.actorRef(clientActor, Success)) + + // FIXME This assumes that someone will actually complete the verification job queue. + // FIXME Could we guarantee that the client won't forget to do this? + ver_handle.queue.watchCompletion() + }) } } From 6cfb5bbe89419f0702f892399c52bd92941850f6 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 00:16:24 +0100 Subject: [PATCH 38/79] Cosmetic changes in VerificationWorker --- .../server/core/VerificationWorker.scala | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index 877e197..65fe81a 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -114,7 +114,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program /** Run the backend verification functionality * */ - def execute(args: Seq[String]) { + def execute(args: Seq[String]): Unit = { val (head, tail) = args.splitAt(args.length-1) val fileless_args = head ++ Seq("--ignoreFile") ++ tail _frontend.setStartTime() @@ -176,23 +176,31 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program s" methodsToVerify: ${methodsToVerify.map(_.name)}.") _frontend.logger.trace(s"The cached program is equivalent to: \n${transformed_prog.toString()}") - _frontend.setVerificationResult(_frontend.verifier.verify(transformed_prog)) + val ver_result: VerificationResult = _frontend.verifier.verify(transformed_prog) + _frontend.setVerificationResult(ver_result) _frontend.setState(DefaultStates.Verification) + _frontend.logger.debug(s"Latest verification result: $ver_result") + // update cache methodsToVerify.foreach((m: Method) => { // Results come back irrespective of program Member. - val cachable_errors = - for { - verRes <- _frontend.getVerificationResult - cache_errs <- verRes match { - case Failure(errs) => getMethodSpecificErrors(m, errs) - case Success => Some(Nil) - } - } yield cache_errs + val cacheable_errors: Option[List[AbstractVerificationError]] = for { + verRes <- _frontend.getVerificationResult + cache_errs <- verRes match { + case Failure(errs) => + val r = getMethodSpecificErrors(m, errs) + _frontend.logger.debug(s"getMethodSpecificErrors returned $r") + r + case Success => + Some(Nil) + } + } yield cache_errs + + _frontend.logger.debug(s"Obtained cacheable errors: $cacheable_errors") - if (cachable_errors.isDefined) { - ViperCache.update(backendName, file, m, transformed_prog, cachable_errors.get) match { + if (cacheable_errors.isDefined) { + ViperCache.update(backendName, file, m, transformed_prog, cacheable_errors.get) match { case e :: es => _frontend.logger.trace(s"Storing new entry in cache for method (${m.name}): $e. Other entries for this method: ($es)") case Nil => @@ -221,11 +229,13 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program * if the error belongs to the method and return None. */ private def getMethodSpecificErrors(m: Method, errors: Seq[AbstractError]): Option[List[AbstractVerificationError]] = { - val methodPos = m.pos match { - case sp: SourcePosition => Some(sp.start.line, sp.end.get.line) - case _ => { + val methodPos: Option[(Int, Int)] = m.pos match { + case sp: SourcePosition => + /** Only the line component matters (not the column) since, + * in Viper, each method must be declared on a new line. */ + Some(sp.start.line, sp.end.get.line) + case _ => None - } } val result = scala.collection.mutable.ListBuffer[AbstractVerificationError]() @@ -237,12 +247,13 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program e.pos match { case pos: HasLineColumn => val errorPos = pos.line - if (methodPos.isEmpty) - { + if (methodPos.isEmpty) { return None } // The position of the error is used to determine to which Method it belongs. - if (errorPos >= methodPos.get._1 && errorPos <= methodPos.get._2) result += e + if (methodPos.get._1 <= errorPos && errorPos <= methodPos.get._2) { + result += e + } case _ => return None } From 8796cb3a1ebd4cf4f73da468afb3336af6a98ec3 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 00:16:58 +0100 Subject: [PATCH 39/79] Renamed ViperServerSpec as viper.server.core.ViperServerHttpSpec --- .../server/core/ViperServerHttpSpec.scala} | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename src/test/scala/{ViperServerTests.scala => viper/server/core/ViperServerHttpSpec.scala} (97%) diff --git a/src/test/scala/ViperServerTests.scala b/src/test/scala/viper/server/core/ViperServerHttpSpec.scala similarity index 97% rename from src/test/scala/ViperServerTests.scala rename to src/test/scala/viper/server/core/ViperServerHttpSpec.scala index 3f96ce5..8e9daef 100644 --- a/src/test/scala/ViperServerTests.scala +++ b/src/test/scala/viper/server/core/ViperServerHttpSpec.scala @@ -1,3 +1,5 @@ +package viper.server.core + // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -17,7 +19,7 @@ import viper.server.vsi.Requests._ import scala.concurrent.duration._ -class ViperServerSpec extends WordSpec with Matchers with ScalatestRouteTest { +class ViperServerHttpSpec extends WordSpec with Matchers with ScalatestRouteTest { import scala.language.postfixOps implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() From 60de197a6a3d78776fa8cdb5b8ab2503abd2cbc0 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 00:17:26 +0100 Subject: [PATCH 40/79] Renamed ParsingTests as viper.server.core.AstGenerationTests --- .../server/core/AstGenerationTests.scala} | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename src/test/scala/{ParsingTests.scala => viper/server/core/AstGenerationTests.scala} (95%) diff --git a/src/test/scala/ParsingTests.scala b/src/test/scala/viper/server/core/AstGenerationTests.scala similarity index 95% rename from src/test/scala/ParsingTests.scala rename to src/test/scala/viper/server/core/AstGenerationTests.scala index 1b3dec5..49cede9 100644 --- a/src/test/scala/ParsingTests.scala +++ b/src/test/scala/viper/server/core/AstGenerationTests.scala @@ -1,3 +1,5 @@ +package viper.server.core + // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -13,7 +15,7 @@ import viper.silver.ast.Program import viper.silver.logger.ViperStdOutLogger -class ParsingTests extends WordSpec with Matchers with ScalatestRouteTest { +class AstGenerationTests extends WordSpec with Matchers with ScalatestRouteTest { private val verifiableFile = "src/test/resources/viper/let.vpr" private val emptyFile = "src/test/resources/viper/empty.vpr" From bc90fe2e403a2136198e3b0ad1e62cddc9241dea Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 00:19:01 +0100 Subject: [PATCH 41/79] Added AsyncCoreServerSpec; started porting fragile tests from CoreServerTest. --- .../viper/server/utility/AstGenerator.scala | 4 +- src/test/scala/CoreServerTests.scala | 410 ------------------ .../server/core/AsyncCoreServerSpec.scala | 67 +++ .../viper/server/core/CoreServerTest.scala | 339 +++++++++++++++ 4 files changed, 408 insertions(+), 412 deletions(-) delete mode 100644 src/test/scala/CoreServerTests.scala create mode 100644 src/test/scala/viper/server/core/AsyncCoreServerSpec.scala create mode 100644 src/test/scala/viper/server/core/CoreServerTest.scala diff --git a/src/main/scala/viper/server/utility/AstGenerator.scala b/src/main/scala/viper/server/utility/AstGenerator.scala index 4f85be6..42e3fcd 100644 --- a/src/main/scala/viper/server/utility/AstGenerator.scala +++ b/src/main/scala/viper/server/utility/AstGenerator.scala @@ -20,7 +20,7 @@ class AstGenerator(private val _logger: Logger, /** Creates a backend that reads and parses the file */ protected override val _frontend: SilFrontend = { - _logger.info(s"Creating new verification backend.") + _logger.info(s"Creating new AstGenerator instance.") new ViperAstProvider(_reporter) } /** Parses and translates a Viper file into a Viper AST. @@ -43,7 +43,7 @@ class AstGenerator(private val _logger: Logger, if (_frontend.errors.isEmpty) { Some(_frontend.translationResult) } else { - _logger.error(s"There was some error while translating ${_frontend.errors}") + _logger.error(s"An error occurred while translating ${_frontend.errors}") None } } diff --git a/src/test/scala/CoreServerTests.scala b/src/test/scala/CoreServerTests.scala deleted file mode 100644 index 8a2cd3e..0000000 --- a/src/test/scala/CoreServerTests.scala +++ /dev/null @@ -1,410 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) 2011-2020 ETH Zurich. - -package viper.server.core - -import akka.actor.{Actor, ActorSystem, Props} -import akka.http.scaladsl.testkit.ScalatestRouteTest -import org.scalatest.{Matchers, WordSpec} -import viper.server.vsi._ -import viper.server.utility.AstGenerator -import viper.silver.ast.Program -import viper.silver.logger.SilentLogger -import viper.silver.reporter._ - -import scala.concurrent.Future -import scala.util.{Failure, Success} - -class CoreServerTest extends WordSpec with Matchers with ScalatestRouteTest { - - implicit var actor_system: ActorSystem = ActorSystem("Test") - val test_actor_0 = actor_system.actorOf(ClientActor.props(0)) - val test_actor_1 = actor_system.actorOf(ClientActor.props(1)) - val test_actor_2 = actor_system.actorOf(ClientActor.props(2)) - val actor_tests_results: Array[Option[Boolean]] = Array(None, None, None) - - object ClientActor { - case object Terminate - def props(test_no: Int): Props = Props(new ClientActor(test_no)) - } - - class ClientActor(private val test_no: Int) extends Actor { - - override def receive: PartialFunction[Any, Unit] = { - case m: Message => - m match { - case _: OverallSuccessMessage => - actor_tests_results(test_no) = Some(true) - case _: OverallFailureMessage => - actor_tests_results(test_no) = Some(false) - case _ => - } - case ClientActor.Terminate => - system.terminate() - } - } - - private val silent_logger = SilentLogger() - - private val ast_gen = new AstGenerator(silent_logger.get) - private val empty_file = "src/test/resources/viper/empty.vpr" - private val sum_file = "src/test/resources/viper/sum_method.vpr" - private val verificationError_file = "src/test/resources/viper/verification_error.vpr" - - private val empty_ast = ast_gen.generateViperAst(empty_file).get - private val sum_ast = ast_gen.generateViperAst(sum_file).get - private val verificationError_ast = ast_gen.generateViperAst(verificationError_file).get - - private val noCache_backend = SiliconConfig(List("--disableCaching")) - private val cache_backend = SiliconConfig(List()) - - private val empty_args: Array[String] = Array() - - "An instance of ViperCoreServer" when { - "verifying a single program with caching disabled" should { - val core = new ViperCoreServer(empty_args) - - "be able to execute 'start()' without exceptions" in { - core.start() - } - - var jid: VerJobId = null - "be able to execute 'verify()' without exceptions" in { - jid = core.verify(verificationError_file, noCache_backend, verificationError_ast) - assert(jid != null) - } - - "be able to have 'verify()' return a JobHandler with non-negative id" in { - assert(jid.id >= 0) - } - - var messages_future: Future[Seq[Message]] = null - "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages." in { - messages_future = ViperCoreServerUtils.getMessagesFuture(core, jid) - assert(messages_future != null) - } - - "eventually see the future returned from 'getMessagesFuture()' completed successfully" in { - while (!messages_future.isCompleted) { - Thread.sleep(100) - } - messages_future.onComplete({ - case Success(_) => succeed - case Failure(e) => fail(e) - }) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - - "not be able to execute 'verify()' after 'stop()' without exceptions" in { - assertThrows[IllegalStateException] { - core.verify(verificationError_file, noCache_backend, verificationError_ast) - } - } - } - - "verifying a single program with caching enabled" should { - val core = new ViperCoreServer(empty_args) - - "be able to execute 'start()' without exceptions" in { - core.start() - } - - var jid: VerJobId = null - "be able to execute 'verify()' without exceptions" in { - jid = core.verify(sum_file, cache_backend, sum_ast) - assert(jid != null) - } - - "be able to have 'verify()' return a JobHandler with non-negative id" in { - assert(jid.id >= 0) - } - - var messages_future: Future[Seq[Message]] = null - "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages" in { - messages_future = ViperCoreServerUtils.getMessagesFuture(core, jid) - assert(messages_future != null) - } - - "see the future returned from 'getMessagesFuture()' eventually completed successfully" in { - while (!messages_future.isCompleted) { - Thread.sleep(100) - } - messages_future.onComplete({ - case Success(_) => succeed - case Failure(e) => fail(e) - }) - } - - "see the future returned by 'getMessagesFuture()' eventually complete unsuccessfully for an inexistent job" in { - val wrong_jid = VerJobId(42) - messages_future = ViperCoreServerUtils.getMessagesFuture(core, wrong_jid) - while (!messages_future.isCompleted) { - Thread.sleep(100) - } - messages_future.onComplete({ - case Success(_) => fail() - case Failure(_) => succeed - }) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - - "not be able to execute 'verify()' after 'stop()' without exceptions" in { - assertThrows[IllegalStateException] { - core.verify(sum_file, noCache_backend, sum_ast) - } - } - } - - "verifying multiple programs with caching disabled and retrieving results via 'getMessagesFuture()'" should { - val files: List[String] = List(empty_file, sum_file, verificationError_file) - val programs: List[Program] = List(empty_ast, sum_ast, verificationError_ast) - - val core = new ViperCoreServer(empty_args) - core.start() - - val filesAndProgs: List[(String, Program)] = files.zip(programs) - var handlers: List[VerJobId] = null - "be able to have 'verify()' executed repeatedly without exceptions" in { - handlers = filesAndProgs map { case (f, p) => core.verify(f, noCache_backend, p) } - } - - "be able to have 'verify()' return JobHandlers with unique non-negative ids" in { - assert(handlers(0).id == 0) - assert(handlers(1).id == 1) - assert(handlers(2).id == 2) - } - - "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages containing the expected verification result" in { - val messages_futures: List[Future[Seq[Message]]] = handlers.map(h => { - ViperCoreServerUtils.getMessagesFuture(core, h) - }) - val filesAndFutures = files.zip(messages_futures) - filesAndFutures.foreach({ case (f, mf) => - while (!mf.isCompleted) { - Thread.sleep(100) - } - mf.onComplete({ - case Success(messages) => - messages.last match { - case _: OverallSuccessMessage => - assert(f != verificationError_file) - case _: OverallFailureMessage => - assert(f == verificationError_file) - case _ => fail() - } - case Failure(e) => fail(e) - }) - }) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - } - - "verifying multiple programs with caching enabled and retrieving results via 'getMessagesFuture()'" should { - val files: List[String] = List(empty_file, sum_file, verificationError_file) - val programs: List[Program] = List(empty_ast, sum_ast, verificationError_ast) - - val core = new ViperCoreServer(empty_args) - core.start() - - val filesAndProgs: List[(String, Program)] = files.zip(programs) - var handlers: List[VerJobId] = null - "be able to have 'verify()' executed repeatedly without exceptions" in { - handlers = filesAndProgs map { case (f, p) => core.verify(f, noCache_backend, p) } - } - - "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages containing the expected verification result" in { - val messages_futures: List[Future[Seq[Message]]] = handlers.map(h => { - ViperCoreServerUtils.getMessagesFuture(core, h) - }) - val filesAndFutures = files.zip(messages_futures) - filesAndFutures.foreach({ case (f, mf) => - while (!mf.isCompleted) { - Thread.sleep(100) - } - mf.onComplete({ - case Success(messages) => - messages.last match { - case _: OverallSuccessMessage => - assert(f != verificationError_file) - case _: OverallFailureMessage => - assert(f == verificationError_file) - case _ => fail() - } - case Failure(e) => fail(e) - }) - }) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - } - - "verifying multiple programs with caching disabled and retrieving results via 'streamMessages()" should { - val file1 = empty_file - val file2 = sum_file - val file3 = verificationError_file - - val ast1 = empty_ast - val ast2 = sum_ast - val ast3 = verificationError_ast - - val core = new ViperCoreServer(empty_args) - core.start() - - val jid1 = core.verify(file1, noCache_backend, ast1) - val jid2 = core.verify(file2, noCache_backend, ast2) - val jid3 = core.verify(file3, noCache_backend, ast3) - - var streamOption1: Option[Future[Unit]] = null - var streamOption2: Option[Future[Unit]] = null - var streamOption3: Option[Future[Unit]] = null - - var streamState1: Future[Unit] = null - var streamState2: Future[Unit] = null - var streamState3: Future[Unit] = null - - "be able to have 'streamMessages()' stream a sequence of Viper messages without errors" in { - streamOption1 = core.streamMessages(jid1, test_actor_0) - streamOption2 = core.streamMessages(jid2, test_actor_1) - streamOption3 = core.streamMessages(jid3, test_actor_2) - } - - "have the option returned by 'streamMessages()' be defined" in { - streamState1 = streamOption1.getOrElse(fail()) - streamState2 = streamOption2.getOrElse(fail()) - streamState3 = streamOption3.getOrElse(fail()) - } - - "eventually have future returned by 'streamMessages()' be completed" in { - def allCompleted(): Boolean = { - streamState1.isCompleted && - streamState2.isCompleted && - streamState3.isCompleted - } - - while(!allCompleted()){ - Thread.sleep(500) - } - } - - "have the stream of messages contain the expected verification result" in { - Thread.sleep(2000) - assert(actor_tests_results(0) == Some(true)) - assert(actor_tests_results(1) == Some(true)) - assert(actor_tests_results(2) == Some(false)) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - } - - "verifying an incorrect viper program several times with caching enabled" should { - val core = new ViperCoreServer(empty_args) - core.start() - - "produce an OverallFailure message with a non-empty error list upon first verification." in { - val jid_original = core.verify(verificationError_file, cache_backend, verificationError_ast) - val messages_future_original = ViperCoreServerUtils.getMessagesFuture(core, jid_original) - while (!messages_future_original.isCompleted) { - Thread.sleep(500) - } - messages_future_original.onComplete({ - case Success(messages) => - messages.last match { - case ofm: OverallFailureMessage => - assert(ofm.result.errors.nonEmpty) - case _ => fail() - } - case Failure(e) => fail(e) - }) - } - - "produce an EntityFailure message with a set cached flag when reverified." in { - val jid_cached = core.verify(verificationError_file, cache_backend, verificationError_ast) - val messages_future_cached = ViperCoreServerUtils.getMessagesFuture(core, jid_cached) - while (!messages_future_cached.isCompleted) { - Thread.sleep(100) - } - messages_future_cached.onComplete({ - case Success(messages) => - val has_cached_msg = messages.exists { - case EntityFailureMessage(_, _, _, _, true) => true - case _ => false - } - assert(has_cached_msg) - case Failure(e) => fail(e) - }) - } - - "be able to execute 'flushCache()' without exceptions after several verifications" in { - core.flushCache() - } - - "produce an EntityFailure message with cleared cached flag and an OverallFailure message with an non-empty error list when reverified after flushing the cache." in { - val jid_flushed = core.verify(verificationError_file, cache_backend, verificationError_ast) - val messages_future_flushed = ViperCoreServerUtils.getMessagesFuture(core, jid_flushed) - while (!messages_future_flushed.isCompleted) { - Thread.sleep(100) - } - messages_future_flushed.onComplete({ - case Success(messages) => - val has_cached_msg = messages.exists { - case EntityFailureMessage(_, _, _, _, true) => true - case _ => false - } - val has_overall_failure = messages.last match { - case _: OverallFailureMessage => true - case _ => false - } - assert(!has_cached_msg && has_overall_failure) - case Failure(e) => fail(e) - }) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - } - - "maximum capacity of verification jobs is exceeded" should { - val core = new ViperCoreServer(empty_args) - core.start() - - val jid = core.verify(sum_file, noCache_backend, sum_ast) - core.verify(sum_file, noCache_backend, sum_ast) - core.verify(sum_file, noCache_backend, sum_ast) - - "have 'verify()' return a VerificationJobHandler with negative id" in { - val spillHandler = core.verify(sum_file, noCache_backend, sum_ast) - assert(spillHandler.id < 0) - } - - "have 'verify()' return a non-negative id upon freeing up a verification request by calling 'getMessagesFuture()'" in { - val result_future = ViperCoreServerUtils.getMessagesFuture(core, jid) - while (!result_future.isCompleted) { - Thread.sleep(100) - } - val newHandler = core.verify(sum_file, noCache_backend, sum_ast) - assert(newHandler.id > 0) - } - - "be able to execute 'stop()' without exceptions" in { - core.stop() - } - } - } -} \ No newline at end of file diff --git a/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala b/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala new file mode 100644 index 0000000..9572796 --- /dev/null +++ b/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala @@ -0,0 +1,67 @@ +package viper.server.core + +import akka.actor.ActorSystem +import org.scalatest.flatspec.AsyncFlatSpec +import viper.server.core.ViperCoreServerUtils.getMessagesFuture +import viper.server.utility.AstGenerator +import viper.silver.ast.Program +import viper.silver.logger.SilentLogger +import viper.silver.reporter.{EntityFailureMessage, Message, OverallFailureMessage} + +class AsyncCoreServerSpec extends AsyncFlatSpec { + implicit var actor_system: ActorSystem = ActorSystem("Test") + + private val test_file = "src/test/resources/viper/verification_error.vpr" + private val test_ast: Program = (new AstGenerator(SilentLogger().get)).generateViperAst(test_file).get + + val server_args: Array[String] = Array() + val silicon_without_caching: SiliconConfig = SiliconConfig(List("--disableCaching")) + val silicon_with_caching: SiliconConfig = SiliconConfig(List()) + + val core = new ViperCoreServer(server_args) + core.start() + + /* vvvvvvvvvvvvvvvvvvvvvvv */ + + behavior of "ViperCoreServer" + + /* ^^^^^^^^^^^^^^^^^^^^^^^ */ + + it should s"be able to eventually produce an OverallFailureMessage @$test_file" in { + val jid = core.verify(test_file, silicon_with_caching, test_ast) + ViperCoreServerUtils.getMessagesFuture(core, jid) map { + messages: List[Message] => + val ofms = messages collect { + case ofm: OverallFailureMessage => ofm + } + val efms = messages collect { + case efm: EntityFailureMessage => efm + } + assert(ofms.length === 1) + assert(efms.length === 1) + } + } + + it should s"retrieve the cached results upon requesting to verify the same AST" in { + val jid = core.verify(test_file, silicon_with_caching, test_ast) + getMessagesFuture(core, jid) map { + messages: List[Message] => + val efms: List[EntityFailureMessage] = messages collect { + case efm: EntityFailureMessage => efm + } +// println(ViperCache) +// println(efms.last) + assert(efms.length === 1 && efms.last.cached) +// ofms.last.result.errors.collect { case a: AbstractError => a.cached }.length === 1) + } + } + + it should s"run getMessagesFuture() to get Seq[Message] containing the expected verification result" in { + val jid = core.verify(test_file, silicon_without_caching, test_ast) + getMessagesFuture(core, jid) map { + messages: List[Message] => +// println(messages) + assert(true) + } + } +} diff --git a/src/test/scala/viper/server/core/CoreServerTest.scala b/src/test/scala/viper/server/core/CoreServerTest.scala new file mode 100644 index 0000000..e2e5b1d --- /dev/null +++ b/src/test/scala/viper/server/core/CoreServerTest.scala @@ -0,0 +1,339 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + +package viper.server.core + +import akka.actor.{Actor, ActorSystem, Props} +import akka.http.scaladsl.testkit.ScalatestRouteTest +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import viper.server.utility.AstGenerator +import viper.server.vsi.VerJobId +import viper.silver.ast.Program +import viper.silver.logger.SilentLogger +import viper.silver.reporter._ + +import scala.concurrent.duration.DurationInt +import scala.concurrent.{Await, Future} +import scala.language.postfixOps + + +/** + * TODO rewrite all tests in terms of [[AsyncCoreServerSpec]] + * which offers a the (reactive) specification for ViperCoreServer. + */ +class CoreServerTest extends AnyWordSpec with Matchers with ScalatestRouteTest { + + implicit var actor_system: ActorSystem = ActorSystem("Test") + private val test_actors = 0 to 2 map ((i: Int) => actor_system.actorOf(ClientActor.props(i))) + private val expected_results: Array[Option[Boolean]] = Array(Some(true), Some(true), Some(false)) + + object ClientActor { + case object Terminate + case object ReportOutcome + def props(test_no: Int): Props = Props(new ClientActor(test_no)) + } + + class ClientActor(private val test_no: Int) extends Actor { + + private var outcome: Option[Boolean] = None + + override def receive: PartialFunction[Any, Unit] = { + case m: Message => + m match { + case _: OverallSuccessMessage => + outcome = Some(true) + case _: OverallFailureMessage => + outcome = Some(false) + case m => + } + case ClientActor.ReportOutcome => + sender() ! outcome + case ClientActor.Terminate => + system.terminate() + } + } + + private val silent_logger = SilentLogger() + + private val ast_gen = new AstGenerator(silent_logger.get) + + private val ver_error_file = "src/test/resources/viper/verification_error.vpr" + private val empty_viper_file = "src/test/resources/viper/empty.vpr" + private val correct_viper_file = "src/test/resources/viper/sum_method.vpr" + + private val files = List(empty_viper_file, correct_viper_file, ver_error_file) + + private val asts = files.map(ast_gen.generateViperAst(_).get) + + private def getAstByFileName(file: String): Program = + (files zip asts collect { + case (f, ast) if f==file => ast + }).last + + private val noCache_backend = SiliconConfig(List("--disableCaching")) + private val cache_backend = SiliconConfig(List()) + + private val empty_args: Array[String] = Array() + + "An instance of ViperCoreServer" when { + "verifying a single program with caching disabled" should { + val core = new ViperCoreServer(empty_args) + + "be able to execute 'start()' without exceptions" in { + core.start() + } + + var jid: VerJobId = null + "be able to execute 'verify()' without exceptions" in { + jid = core.verify(ver_error_file, noCache_backend, getAstByFileName(ver_error_file)) + assert(jid != null) + } + + "be able to have 'verify()' return a JobHandler with non-negative id" in { + assert(jid.id >= 0) + } + + var messages_future: Future[Seq[Message]] = null + "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages." in { + messages_future = ViperCoreServerUtils.getMessagesFuture(core, jid) + assert(messages_future != null) + } + + "eventually see the future returned from 'getMessagesFuture()' completed successfully" in { + while (!messages_future.isCompleted) { + Thread.sleep(100) + } + messages_future.onComplete({ + case scala.util.Success(_) => succeed + case scala.util.Failure(e) => fail(e) + }) + } + + "be able to execute 'stop()' without exceptions" in { + core.stop() + } + + "not be able to execute 'verify()' after 'stop()' without exceptions" in { + assertThrows[IllegalStateException] { + core.verify(ver_error_file, noCache_backend, getAstByFileName(ver_error_file)) + } + } + } + + "verifying a single program with caching enabled" should { + val core = new ViperCoreServer(empty_args) + + "be able to execute 'start()' without exceptions" in { + core.start() + } + + var jid: VerJobId = null + "be able to execute 'verify()' without exceptions" in { + jid = core.verify(correct_viper_file, cache_backend, getAstByFileName(correct_viper_file)) + assert(jid != null) + } + + "be able to have 'verify()' return a JobHandler with non-negative id" in { + assert(jid.id >= 0) + } + + var messages_future: Future[Seq[Message]] = null + "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages" in { + messages_future = ViperCoreServerUtils.getMessagesFuture(core, jid) + assert(messages_future != null) + } + + "see the future returned from 'getMessagesFuture()' eventually completed successfully" in { + while (!messages_future.isCompleted) { + Thread.sleep(100) + } + messages_future.onComplete { + case scala.util.Success(_) => succeed + case scala.util.Failure(e) => fail(e) + } + } + + "see the future returned by 'getMessagesFuture()' eventually complete unsuccessfully for an inexistent job" in { + val wrong_jid = VerJobId(42) + messages_future = ViperCoreServerUtils.getMessagesFuture(core, wrong_jid) + while (!messages_future.isCompleted) { + Thread.sleep(100) + } + messages_future.onComplete { + case scala.util.Success(_) => fail() + case scala.util.Failure(_) => succeed + } + } + + "be able to execute 'stop()' without exceptions" in { + core.stop() + } + + "not be able to execute 'verify()' after 'stop()' without exceptions" in { + assertThrows[IllegalStateException] { + core.verify(correct_viper_file, noCache_backend, getAstByFileName(correct_viper_file)) + } + } + } + + "verifying multiple programs with caching disabled and retrieving results via 'getMessagesFuture()'" should { + + val core = new ViperCoreServer(empty_args) + core.start() + + var handlers: List[VerJobId] = null + "be able to have 'verify()' executed repeatedly without exceptions" in { + handlers = (files zip asts) map { case (f, p) => core.verify(f, noCache_backend, p) } + } + + "be able to have 'verify()' return JobHandlers with unique non-negative ids" in { + assert(handlers(0).id == 0) + assert(handlers(1).id == 1) + assert(handlers(2).id == 2) + } + + /** Does not terminate. Rewritten in [[AsyncCoreServerSpec]]. */ +// "be able to have 'getMessagesFuture()' return a future of a sequence of Viper messages containing the expected verification result" in { +// val messages_futures: List[Future[Seq[Message]]] = handlers.map(h => { +// ViperCoreServerUtils.getMessagesFuture(core, h) +// }) +// files.zip(messages_futures).foreach({ case (f, mf) => +// while (!mf.isCompleted) { +// Thread.sleep(100) +// } +// mf.onComplete({ +// case scala.util.Success(messages) => +// messages.last match { +// case _: OverallSuccessMessage => +// assert(f != ver_error_file) +// case _: OverallFailureMessage => +// assert(f == ver_error_file) +// case _ => fail() +// } +// case scala.util.Failure(e) => fail(e) +// }) +// }) +// } + + "be able to execute 'stop()' without exceptions" in { + core.stop() + } + } + + "verifying an incorrect Viper program several times with caching enabled" should { + val core = new ViperCoreServer(empty_args) + core.start() + + "produce an OverallFailure message with a non-empty error list upon first verification." in { + val jid_original = core.verify(ver_error_file, cache_backend, getAstByFileName(ver_error_file)) + + val messages_fut = ViperCoreServerUtils.getMessagesFuture(core, jid_original) + + messages_fut.onComplete { + case scala.util.Success(messages) => + messages.last match { + case ofm: OverallFailureMessage => + assert(ofm.result.errors.nonEmpty) + case _ => + fail("last message in stream must be of type OverallFailureMessage") + } + case scala.util.Failure(e) => + fail(e) + } + + Await.ready(messages_fut, 5 seconds) + } + + /** Rewritten in [[AsyncCoreServerSpec]] (but currently fails due to Silver issue#489) */ +// "produce an EntityFailure message with a set cached flag when re-verified." in { +// +// val jid_cached = files zip asts collect { +// case (file, ast) if file == ver_error_file => +// core.verify(ver_error_file, cache_backend, ast) +// } last +// +// val messages_fut = ViperCoreServerUtils.getMessagesFuture(core, jid_cached) +// +// messages_fut.onComplete { +// case scala.util.Success(messages) => +// val has_cached_msg = messages.exists { +// case EntityFailureMessage(_, _, _, _, true) => true +// case _ => false +// } +// has_cached_msg should be (true) +// case scala.util.Failure(e) => fail(e) +// } +// +// Await.ready(messages_fut, 3 seconds) +// } + + "be able to execute 'flushCache()' without exceptions after several verifications" in { + core.flushCache() + } + + "produce an EntityFailure message with cleared cached flag and an OverallFailure message with an non-empty error list when reverified after flushing the cache." in { + + val jid_flushed = files zip asts collect { + case (file, ast) if file == ver_error_file => + core.verify(ver_error_file, cache_backend, ast) + } last + + val messages_fut = ViperCoreServerUtils.getMessagesFuture(core, jid_flushed) + + Await.result(messages_fut, 5 seconds) match { + case messages: List[Message] => + val has_cached_msg = messages.exists { + case EntityFailureMessage(_, _, _, _, true) => + true + case _ => + false + } + val has_overall_failure = messages.last match { + case _: OverallFailureMessage => true + case _ => false + } + has_cached_msg should be (false) + has_overall_failure should be (true) + } + } + + "be able to execute 'stop()' without exceptions" in { + core.stop() + } + } + + "maximum capacity of verification jobs is exceeded" should { + val core = new ViperCoreServer(empty_args) + core.start() + + val (test_file, test_ast) = files zip asts collect { + case (file, ast) if file == ver_error_file => (file, ast) + } last + + val jid = 1 to core.config.maximumActiveJobs() map { + _ => core.verify(test_file, noCache_backend, test_ast) + } last + + "have 'verify()' return a VerificationJobHandler with negative id" in { + val spillHandler = core.verify(test_file, noCache_backend, test_ast) + assert(spillHandler.id < 0) + } + + "have 'verify()' return a non-negative id upon freeing up a verification request by calling 'getMessagesFuture()'" in { + val result_fut = ViperCoreServerUtils.getMessagesFuture(core, jid) + Await.ready(result_fut, 5 seconds) + + val newHandler = core.verify(test_file, noCache_backend, test_ast) + assert(newHandler.id > 0) + } + + "be able to execute 'stop()' without exceptions" in { + core.stop() + } + } + } +} \ No newline at end of file From b72d78c3fdb590bc75627cc2d03066f1a49a406c Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 00:22:49 +0100 Subject: [PATCH 42/79] Some more cosmetic stuff --- .../scala/viper/server/core/ViperCoreServerUtils.scala | 6 +----- src/main/scala/viper/server/vsi/JobActor.scala | 10 ++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala index ea24051..2199c3f 100644 --- a/src/main/scala/viper/server/core/ViperCoreServerUtils.scala +++ b/src/main/scala/viper/server/core/ViperCoreServerUtils.scala @@ -50,11 +50,7 @@ object ViperCoreServerUtils { val complete_future = core.streamMessages(jid, actor).getOrElse(return Future.failed(JobNotFoundException)) val res: Future[List[Message]] = complete_future.flatMap(_ => { implicit val askTimeout: Timeout = Timeout(core.config.actorCommunicationTimeout() milliseconds) - val answer: Future[Any] = actor ? SeqActor.Result - //recover result type from "Any" - answer.map({ - case res: List[Message] => res - }) + (actor ? SeqActor.Result).mapTo[List[Message]] }) res } diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index 048a0f0..b7fe3e7 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -19,8 +19,6 @@ class JobActor[T](private val id: JobId) extends Actor { import VerificationProtocol._ - - private var _astConstructionTask: TaskThread[T] = _ private var _verificationTask: TaskThread[T] = _ @@ -58,14 +56,14 @@ class JobActor[T](private val id: JobId) extends Actor { resetAstConstructionTask() _astConstructionTask = req.task _astConstructionTask.start() - sender ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact) + sender() ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact) case ver_req: Verify[T] => //println(">>> JobActor received request Verify") resetVerificationTask() _verificationTask = ver_req.task _verificationTask.start() - sender ! VerHandle(self, ver_req.queue, ver_req.publisher, ver_req.prev_job_id) + sender() ! VerHandle(self, ver_req.queue, ver_req.publisher, ver_req.prev_job_id) } case req: StopProcessRequest => val did_I_interrupt = req match { @@ -75,10 +73,10 @@ class JobActor[T](private val id: JobId) extends Actor { interrupt(_verificationTask) } if (did_I_interrupt) { - sender ! s"$id has been successfully interrupted." + sender() ! s"$id has been successfully interrupted." } else { // FIXME: Saying this is a potential vulnerability - sender ! s"$id has already been finalized." + sender() ! s"$id has already been finalized." } case msg => throw new Exception("JobActor: received unexpected message: " + msg) From a678c442ce98ee5e37acf4ea0b631f22f6a1fe39 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 23:34:46 +0100 Subject: [PATCH 43/79] Address Linard's points --- src/main/scala/viper/server/core/AstWorker.scala | 2 +- .../scala/viper/server/core/MessageReportingTask.scala | 3 +-- src/main/scala/viper/server/core/VerificationWorker.scala | 2 +- src/main/scala/viper/server/core/ViperBackendConfig.scala | 8 +++----- .../scala/viper/server/frontends/http/ViperRequests.scala | 6 ++++++ .../frontends/http/jsonWriters/AlloySolutionWriter.scala | 6 ++++++ .../http/jsonWriters/SymbExLogReportWriter.scala | 6 ++++++ .../server/frontends/http/jsonWriters/TermWriter.scala | 6 ++++++ .../frontends/http/jsonWriters/ViperIDEProtocol.scala | 6 ++++++ src/main/scala/viper/server/vsi/JobActor.scala | 4 ++-- .../scala/viper/server/vsi/MessageStreamingTask.scala | 2 +- 11 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala index 3093f3b..07dfb35 100644 --- a/src/main/scala/viper/server/core/AstWorker.scala +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -23,7 +23,7 @@ class AstWorker(val arg_list: List[String], val logger: Logger)(implicit val ec: ExecutionContext) extends MessageReportingTask { private val _artifact_pro: Promise[Program] = Promise() - override def artifact: Future[Program] = _artifact_pro.future + override def artifact: Option[Future[Program]] = Some(_artifact_pro.future) private def constructAst(): Future[Program] = Future { diff --git a/src/main/scala/viper/server/core/MessageReportingTask.scala b/src/main/scala/viper/server/core/MessageReportingTask.scala index 4367109..0d1197b 100644 --- a/src/main/scala/viper/server/core/MessageReportingTask.scala +++ b/src/main/scala/viper/server/core/MessageReportingTask.scala @@ -6,8 +6,6 @@ import viper.silver.reporter.{EntityFailureMessage, EntitySuccessMessage, Messag trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost { -// private val _reporter: - protected def enqueueMessage(msg: Message): Unit = { super.enqueueMessage(pack(msg)) } @@ -17,6 +15,7 @@ trait MessageReportingTask extends MessageStreamingTask[Program] with ViperPost val name = s"ViperServer_$tag" def report(msg: Message): Unit = { + //TODO use logger //println(s">>> ActorReporter.report($msg)") msg match { case m: EntityFailureMessage if m.concerning.info.isCached => diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index 65fe81a..b516b4a 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -34,7 +34,7 @@ class VerificationWorker(private val logger: Logger, private val program: Program)(implicit val ec: ExecutionContext) extends MessageReportingTask { - override def artifact: Future[Program] = Future.successful(program) + override def artifact: Option[Future[Nothing]] = None private var backend: ViperBackend = _ private def resolveCustomBackend(clazzName: String, rep: Reporter): Option[SilFrontend] = { diff --git a/src/main/scala/viper/server/core/ViperBackendConfig.scala b/src/main/scala/viper/server/core/ViperBackendConfig.scala index 225c018..b30bb8d 100644 --- a/src/main/scala/viper/server/core/ViperBackendConfig.scala +++ b/src/main/scala/viper/server/core/ViperBackendConfig.scala @@ -21,16 +21,14 @@ trait ViperBackendConfig { } } -//object EmptyConfig extends ViperBackendConfig { -// override val backend_name = "empty_config" -// val partialCommandLine: List[String] = Nil -//} case class SiliconConfig(partialCommandLine: List[String]) extends ViperBackendConfig { override val backend_name = "silicon" } + case class CarbonConfig(partialCommandLine: List[String]) extends ViperBackendConfig { override val backend_name = "carbon" } + case class CustomConfig(partialCommandLine: List[String], backend_name: String) extends ViperBackendConfig object ViperBackendConfig { @@ -42,6 +40,6 @@ object ViperBackendConfig { case custom :: args => CustomConfig(args, custom) case invalid => throw new IllegalArgumentException(s"cannot build ViperConfig from string `$invalid`") } - + def apply(input: String): ViperBackendConfig = apply(getArgListFromArgString(input)) } \ No newline at end of file diff --git a/src/main/scala/viper/server/frontends/http/ViperRequests.scala b/src/main/scala/viper/server/frontends/http/ViperRequests.scala index 1a4d128..1825ac5 100644 --- a/src/main/scala/viper/server/frontends/http/ViperRequests.scala +++ b/src/main/scala/viper/server/frontends/http/ViperRequests.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.frontends.http import spray.json.{DefaultJsonProtocol, RootJsonFormat} diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala index dce03e9..b46a5ff 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/AlloySolutionWriter.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.frontends.http.jsonWriters import edu.mit.csail.sdg.ast.Sig.Field diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala index 5546984..d5de61a 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/SymbExLogReportWriter.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.frontends.http.jsonWriters import spray.json.{JsArray, JsField, JsNull, JsObject, JsString, JsValue} diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala index 194821a..b474740 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/TermWriter.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.frontends.http.jsonWriters import spray.json.{JsArray, JsNull, JsObject, JsString, JsValue} diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index 4c5e37b..32f1a7f 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.frontends.http.jsonWriters import akka.NotUsed diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index b7fe3e7..857bd56 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -6,7 +6,7 @@ import scala.concurrent.Future class TaskThread[T](private val _task: MessageStreamingTask[T]) extends Thread(_task) { - def getArtifact: Future[T] = _task.artifact + def getArtifact: Option[Future[T]] = _task.artifact } // --- Actor: JobActor --- @@ -56,7 +56,7 @@ class JobActor[T](private val id: JobId) extends Actor { resetAstConstructionTask() _astConstructionTask = req.task _astConstructionTask.start() - sender() ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact) + sender() ! AstHandle(self, req.queue, req.publisher, _astConstructionTask.getArtifact.get) case ver_req: Verify[T] => //println(">>> JobActor received request Verify") diff --git a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala index b0adf28..9c8bb0b 100644 --- a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -24,7 +24,7 @@ import scala.concurrent.duration._ * */ abstract class MessageStreamingTask[T]()(implicit val executionContext: ExecutionContext) extends Runnable with Post { - def artifact: Future[T] + def artifact: Option[Future[T]] private var q_actor: ActorRef = _ From 81c8c69ba1f4eae2f9ae0a5f01709e5fa9b077e9 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Mon, 28 Dec 2020 23:48:05 +0100 Subject: [PATCH 44/79] Update README.md --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cfd143d..31af071 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is ViperServer, an HTTP server that manages verification requests to different tools from the Viper tool stack. -The main two tools currently are: +The main two Viper tools (a.k.a verification backends) currently are: - [Carbon](https://bitbucket.org/viperproject/carbon), a verification condition generation (VCG) backend for the Viper language. - [Silicon](https://bitbucket.org/viperproject/silicon), a symbolic execution verification backend. @@ -14,13 +14,16 @@ The main two tools currently are: 1. Viper IDE: integration of Viper into Visual Studio Code (VS Code). Viper IDE provides the best user experience for Viper. More details here: http://viper.ethz.ch/downloads/ -2. Avoid 1-3 second delays caused by JVM startup time. ViperServer offers a robust alternative to, e.g., +1. Facilitate the development of verification IDEs for Viper frontends, such as: + - [Gobra](https://github.com/viperproject/gobra), the Viper-based verifier for the Go language + - [Prusti](https://github.com/viperproject/prusti-dev/), the Viper-based verifier for the Rust language +1. Avoid 1-3 second delays caused by JVM startup time. ViperServer offers a robust alternative to, e.g., [Nailgun](https://github.com/facebook/nailgun). -3. Develop Viper encodings more efficiently with caching. -4. Interact with Viper tools programmatically using the HTTP API. A reference client implementation (in Python) is +1. Develop Viper encodings more efficiently with caching. +1. Interact with Viper tools programmatically using the HTTP API. A reference client implementation (in Python) is available via [viper_client](https://bitbucket.org/viperproject/viper_client). -For more details, please visit: http://viper.ethz.ch/downloads/ +For more details about using Viper, please visit: http://viper.ethz.ch/downloads/ ### Installation Instructions ### From a6a23da4c4cc066168c6a334a7d5d1829a23effd Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 29 Dec 2020 00:01:18 +0100 Subject: [PATCH 45/79] Fixed the license headers --- .../scala/viper/server/LanguageServerRunner.scala | 13 +++++-------- src/main/scala/viper/server/core/AstWorker.scala | 7 ++++++- .../viper/server/core/MessageReportingTask.scala | 6 ++++++ src/main/scala/viper/server/core/ViperPost.scala | 6 ++++++ .../server/frontends/lsp/CommandProtocol.scala | 12 +++++------- .../scala/viper/server/frontends/lsp/Common.scala | 12 +++++------- .../viper/server/frontends/lsp/Coordinator.scala | 12 +++++------- .../viper/server/frontends/lsp/DataProtocol.scala | 12 +++++------- .../viper/server/frontends/lsp/FileManager.scala | 12 +++++------- .../server/frontends/lsp/IdeLanguageClient.scala | 12 +++++------- .../scala/viper/server/frontends/lsp/IdeLog.scala | 12 +++++------- .../scala/viper/server/frontends/lsp/Progress.scala | 12 +++++------- .../scala/viper/server/frontends/lsp/Receiver.scala | 12 +++++------- .../server/frontends/lsp/ViperServerService.scala | 12 +++++------- src/main/scala/viper/server/utility/Helpers.scala | 6 ++++++ .../server/utility/ProgramDefinitionsProvider.scala | 6 ++++++ src/main/scala/viper/server/vsi/JobActor.scala | 6 ++++++ src/main/scala/viper/server/vsi/JobPool.scala | 6 ++++++ .../viper/server/vsi/MessageStreamingTask.scala | 6 ++++++ src/main/scala/viper/server/vsi/QueueActor.scala | 6 ++++++ src/main/scala/viper/server/vsi/Terminator.scala | 6 ++++++ .../viper/server/core/AsyncCoreServerSpec.scala | 6 ++++++ 22 files changed, 121 insertions(+), 79 deletions(-) diff --git a/src/main/scala/viper/server/LanguageServerRunner.scala b/src/main/scala/viper/server/LanguageServerRunner.scala index 08120bd..1f5b7fa 100644 --- a/src/main/scala/viper/server/LanguageServerRunner.scala +++ b/src/main/scala/viper/server/LanguageServerRunner.scala @@ -1,11 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ - +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. import java.io.IOException import java.net.Socket diff --git a/src/main/scala/viper/server/core/AstWorker.scala b/src/main/scala/viper/server/core/AstWorker.scala index 07dfb35..7f8f942 100644 --- a/src/main/scala/viper/server/core/AstWorker.scala +++ b/src/main/scala/viper/server/core/AstWorker.scala @@ -1,5 +1,10 @@ -package viper.server.core +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. +package viper.server.core import ch.qos.logback.classic.Logger import viper.server.utility.AstGenerator diff --git a/src/main/scala/viper/server/core/MessageReportingTask.scala b/src/main/scala/viper/server/core/MessageReportingTask.scala index 0d1197b..5171327 100644 --- a/src/main/scala/viper/server/core/MessageReportingTask.scala +++ b/src/main/scala/viper/server/core/MessageReportingTask.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.core import viper.server.vsi.MessageStreamingTask diff --git a/src/main/scala/viper/server/core/ViperPost.scala b/src/main/scala/viper/server/core/ViperPost.scala index 6a828ef..9a9b612 100644 --- a/src/main/scala/viper/server/core/ViperPost.scala +++ b/src/main/scala/viper/server/core/ViperPost.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.core import viper.server.vsi.{Envelope, Post} diff --git a/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala index 63d4fa3..adb4a72 100644 --- a/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/CommandProtocol.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/Common.scala b/src/main/scala/viper/server/frontends/lsp/Common.scala index 8ccf846..02f88e3 100644 --- a/src/main/scala/viper/server/frontends/lsp/Common.scala +++ b/src/main/scala/viper/server/frontends/lsp/Common.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala index d7eeea8..faa517d 100644 --- a/src/main/scala/viper/server/frontends/lsp/Coordinator.scala +++ b/src/main/scala/viper/server/frontends/lsp/Coordinator.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala index da326d0..7bfb4f9 100644 --- a/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala +++ b/src/main/scala/viper/server/frontends/lsp/DataProtocol.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/FileManager.scala b/src/main/scala/viper/server/frontends/lsp/FileManager.scala index 8873801..4bae2f5 100644 --- a/src/main/scala/viper/server/frontends/lsp/FileManager.scala +++ b/src/main/scala/viper/server/frontends/lsp/FileManager.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala index 6edc3c0..ff3fc9f 100644 --- a/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLanguageClient.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/IdeLog.scala b/src/main/scala/viper/server/frontends/lsp/IdeLog.scala index 77c8e92..1fdbf3e 100644 --- a/src/main/scala/viper/server/frontends/lsp/IdeLog.scala +++ b/src/main/scala/viper/server/frontends/lsp/IdeLog.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/Progress.scala b/src/main/scala/viper/server/frontends/lsp/Progress.scala index 02a95db..b0a1466 100644 --- a/src/main/scala/viper/server/frontends/lsp/Progress.scala +++ b/src/main/scala/viper/server/frontends/lsp/Progress.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/Receiver.scala b/src/main/scala/viper/server/frontends/lsp/Receiver.scala index 27eeb12..52f4dc0 100644 --- a/src/main/scala/viper/server/frontends/lsp/Receiver.scala +++ b/src/main/scala/viper/server/frontends/lsp/Receiver.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index d06c0bd..305d178 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -1,10 +1,8 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2011-2020 ETH Zurich. - */ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. package viper.server.frontends.lsp diff --git a/src/main/scala/viper/server/utility/Helpers.scala b/src/main/scala/viper/server/utility/Helpers.scala index 5478a02..c4aadf2 100644 --- a/src/main/scala/viper/server/utility/Helpers.scala +++ b/src/main/scala/viper/server/utility/Helpers.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.utility import java.io.File diff --git a/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala b/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala index 7462491..25cfa37 100644 --- a/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala +++ b/src/main/scala/viper/server/utility/ProgramDefinitionsProvider.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.utility import scala.language.postfixOps diff --git a/src/main/scala/viper/server/vsi/JobActor.scala b/src/main/scala/viper/server/vsi/JobActor.scala index 857bd56..3b8b92e 100644 --- a/src/main/scala/viper/server/vsi/JobActor.scala +++ b/src/main/scala/viper/server/vsi/JobActor.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.vsi import akka.actor.{Actor, Props} diff --git a/src/main/scala/viper/server/vsi/JobPool.scala b/src/main/scala/viper/server/vsi/JobPool.scala index 224a9bd..252a65d 100644 --- a/src/main/scala/viper/server/vsi/JobPool.scala +++ b/src/main/scala/viper/server/vsi/JobPool.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.vsi import akka.actor.ActorRef diff --git a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala index 9c8bb0b..492e66c 100644 --- a/src/main/scala/viper/server/vsi/MessageStreamingTask.scala +++ b/src/main/scala/viper/server/vsi/MessageStreamingTask.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.vsi import scala.language.postfixOps diff --git a/src/main/scala/viper/server/vsi/QueueActor.scala b/src/main/scala/viper/server/vsi/QueueActor.scala index 4a44e1a..be9dd6a 100644 --- a/src/main/scala/viper/server/vsi/QueueActor.scala +++ b/src/main/scala/viper/server/vsi/QueueActor.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.vsi import akka.actor.{Actor, PoisonPill, Props} diff --git a/src/main/scala/viper/server/vsi/Terminator.scala b/src/main/scala/viper/server/vsi/Terminator.scala index 2f213de..7fd4f65 100644 --- a/src/main/scala/viper/server/vsi/Terminator.scala +++ b/src/main/scala/viper/server/vsi/Terminator.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.vsi import akka.actor.{Actor, ActorSystem, Props} diff --git a/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala b/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala index 9572796..031785b 100644 --- a/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala +++ b/src/test/scala/viper/server/core/AsyncCoreServerSpec.scala @@ -1,3 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2011-2020 ETH Zurich. + package viper.server.core import akka.actor.ActorSystem From 1246a8ec869d7f4c3b1621ce67e5493325b3ecad Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 29 Dec 2020 00:35:24 +0100 Subject: [PATCH 46/79] Add a comment about a future refactoring possibility for LSP --- .../scala/viper/server/frontends/lsp/ViperServerService.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala index 305d178..596a0ca 100644 --- a/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala +++ b/src/main/scala/viper/server/frontends/lsp/ViperServerService.scala @@ -74,6 +74,7 @@ class ViperServerService(args: Array[String]) extends ViperCoreServer(args) with val astGen = new AstGenerator(_logger.get) var ast_option: Option[Program] = None try { + // TODO use AstWorker instead ast_option = astGen.generateViperAst(file) } catch { case _: java.nio.file.NoSuchFileException => From d94ddde18cbff12784cfb54813f016c9269c0867 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 29 Dec 2020 01:11:56 +0100 Subject: [PATCH 47/79] Add @tailrec to getHashForNode --- src/main/scala/viper/server/core/ViperCache.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/scala/viper/server/core/ViperCache.scala b/src/main/scala/viper/server/core/ViperCache.scala index e2d8f57..41084fa 100644 --- a/src/main/scala/viper/server/core/ViperCache.scala +++ b/src/main/scala/viper/server/core/ViperCache.scala @@ -7,7 +7,6 @@ package viper.server.core import scala.language.postfixOps - import ch.qos.logback.classic.Logger import viper.server.core.ViperCache.logger import viper.server.vsi._ @@ -16,6 +15,7 @@ import viper.silver.utility.CacheHelper import viper.silver.verifier.errors._ import viper.silver.verifier.{AbstractVerificationError, VerificationError, errors} +import scala.annotation.tailrec import scala.collection.mutable.{Map => MutableMap} // ===== CACHE OBJECT ================================================================== @@ -314,8 +314,10 @@ object ViperCacheHelper { * * The second argument list is used for specifying external keys as (backend, file). * This is needed for removing separate parts of the hash table. + * * @see [[forgetFile]]. */ + @tailrec private def getHashForNode(node: Node)(implicit key: String): String = node match { case m: Method => removeBody(m).entityHash case hn: Hashable => hn.entityHash From 465413c2208783003ff7863de826d817e2f56b86 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 29 Dec 2020 01:13:26 +0100 Subject: [PATCH 48/79] Fixed some tests in ViperServerHttpSpec --- .../server/core/ViperServerHttpSpec.scala | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/test/scala/viper/server/core/ViperServerHttpSpec.scala b/src/test/scala/viper/server/core/ViperServerHttpSpec.scala index 8e9daef..08f44ca 100644 --- a/src/test/scala/viper/server/core/ViperServerHttpSpec.scala +++ b/src/test/scala/viper/server/core/ViperServerHttpSpec.scala @@ -13,13 +13,14 @@ import akka.http.scaladsl.common.{EntityStreamingSupport, JsonEntityStreamingSup import akka.http.scaladsl.model.{StatusCodes, _} import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.testkit.TestDuration -import org.scalatest.{Matchers, WordSpec} +import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.matchers.should.Matchers import viper.server.ViperServerRunner import viper.server.vsi.Requests._ import scala.concurrent.duration._ -class ViperServerHttpSpec extends WordSpec with Matchers with ScalatestRouteTest { +class ViperServerHttpSpec extends AnyWordSpec with Matchers with ScalatestRouteTest { import scala.language.postfixOps implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json() @@ -33,6 +34,7 @@ class ViperServerHttpSpec extends WordSpec with Matchers with ScalatestRouteTest println(s">>> ViperServer test request `$req` response in the following response: $res") } + // FIXME this does not work with SBT for some reason def getResourcePath(vpr_file: String): String = { val cross_platform_path = new File(vpr_file) getPath val resource = getClass.getResource(cross_platform_path) @@ -49,14 +51,14 @@ class ViperServerHttpSpec extends WordSpec with Matchers with ScalatestRouteTest "\"" + fname + "\"" } - private val verifiableFile = "viper/let.vpr" - private val nonExistingFile = "viper/bla.vpr" - private val emptyFile = "viper/empty.vpr" + private val verifiableFile = "src/test/resources/viper/let.vpr" + private val nonExistingFile = "2165e0fbd4b980436557b5a6f1a41f68.vpr" + private val emptyFile = "src/test/resources/viper/empty.vpr" private val tool = "silicon" - private val testSimpleViperCode_cmd = s"$tool --disableCaching ${getResourcePath(verifiableFile)}" - private val testEmptyFile_cmd = s"$tool --disableCaching ${getResourcePath(emptyFile)}" - private val testNonExistingFile_cmd = s"$tool --disableCaching ${getResourcePath(nonExistingFile)}" + private val testSimpleViperCode_cmd = s"$tool --disableCaching ${verifiableFile}" + private val testEmptyFile_cmd = s"$tool --disableCaching ${emptyFile}" + private val testNonExistingFile_cmd = s"$tool --disableCaching ${nonExistingFile}" "ViperServer" should { s"start a verification process using `$tool` over a small Viper program" in { @@ -64,15 +66,16 @@ class ViperServerHttpSpec extends WordSpec with Matchers with ScalatestRouteTest //printRequestResponsePair(s"POST, /verify, $testSimpleViperCode_cmd", responseAs[String]) status should ===(StatusCodes.OK) contentType should ===(ContentTypes.`application/json`) + responseAs[String] should not include ("""File not found""") } } "respond with the result for process #0" in { Get("/verify/0") ~> _routsUnderTest ~> check { //printRequestResponsePair(s"GET, /verify/0", responseAs[String]) - responseAs[String] should include (s""""kind":"overall","status":"success","verifier":"$tool"""") status should ===(StatusCodes.OK) contentType should ===(ContentTypes.`application/json`) + responseAs[String] should include (s""""kind":"overall","status":"success","verifier":"$tool"""") } } @@ -81,24 +84,25 @@ class ViperServerHttpSpec extends WordSpec with Matchers with ScalatestRouteTest //printRequestResponsePair(s"POST, /verify, $testEmptyFile_cmd", responseAs[String]) status should ===(StatusCodes.OK) contentType should ===(ContentTypes.`application/json`) + responseAs[String] should not include ("""File not found""") } } "respond with the result for process #1" in { Get("/verify/1") ~> _routsUnderTest ~> check { //printRequestResponsePair(s"GET, /verify/1", responseAs[String]) - responseAs[String] should include (s""""kind":"overall","status":"success","verifier":"$tool"""") status should ===(StatusCodes.OK) contentType should ===(ContentTypes.`application/json`) + responseAs[String] should include (s""""kind":"overall","status":"success","verifier":"$tool"""") } } s"start another verification process using `$tool` on an non-existent file" in { Post("/verify", VerificationRequest(testNonExistingFile_cmd)) ~> _routsUnderTest ~> check { //printRequestResponsePair(s"POST, /verify, $testEmptyFile_cmd", responseAs[String]) - responseAs[String] should include (s"not found") status should ===(StatusCodes.OK) contentType should ===(ContentTypes.`application/json`) + responseAs[String] should include (s"not found") } } From 1130886d3f3a8a11354a149dd07f445e564d82cd Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 15:36:54 +0100 Subject: [PATCH 49/79] ViperCache: do not add entries unless errors are correctly mapped to methods. - Cosmetic edits. --- .../server/core/VerificationWorker.scala | 43 ++++++++++++++----- .../scala/viper/server/core/ViperCache.scala | 2 +- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/main/scala/viper/server/core/VerificationWorker.scala b/src/main/scala/viper/server/core/VerificationWorker.scala index b516b4a..926f327 100644 --- a/src/main/scala/viper/server/core/VerificationWorker.scala +++ b/src/main/scala/viper/server/core/VerificationWorker.scala @@ -159,7 +159,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program val cached_errors = result.verification_errors if (cached_errors.isEmpty) { _frontend.reporter report - CachedEntityMessage(_frontend.getVerifierName,result.method, Success) + CachedEntityMessage(_frontend.getVerifierName, result.method, Success) } else { all_cached_errors ++= cached_errors _frontend.reporter report @@ -182,8 +182,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program _frontend.logger.debug(s"Latest verification result: $ver_result") - // update cache - methodsToVerify.foreach((m: Method) => { + val meth_to_err_map: Seq[(Method, Option[List[AbstractVerificationError]])] = methodsToVerify.map((m: Method) => { // Results come back irrespective of program Member. val cacheable_errors: Option[List[AbstractVerificationError]] = for { verRes <- _frontend.getVerificationResult @@ -197,17 +196,39 @@ class ViperBackend(private val _frontend: SilFrontend, private val _ast: Program } } yield cache_errs - _frontend.logger.debug(s"Obtained cacheable errors: $cacheable_errors") + (m, cacheable_errors) + }) + + // Check that the mapping from errors to methods is not messed up + // (otherwise it is unsafe to cache the results) + val update_cache_criterion: Boolean = { + val all_errors_in_file = meth_to_err_map.flatMap(_._2).flatten + _frontend.getVerificationResult.get match { + case Success => + all_errors_in_file.isEmpty + case Failure(errors) => + // FIXME find a better sorting criterion + errors.sortBy(ae => ae.hashCode()) == all_errors_in_file.sortBy(ae => ae.hashCode()) + } + } + + if (update_cache_criterion) { + // update cache + meth_to_err_map.foreach { case (m: Method, cacheable_errors: Option[List[AbstractVerificationError]]) => + _frontend.logger.debug(s"Obtained cacheable errors: $cacheable_errors") - if (cacheable_errors.isDefined) { - ViperCache.update(backendName, file, m, transformed_prog, cacheable_errors.get) match { - case e :: es => - _frontend.logger.trace(s"Storing new entry in cache for method (${m.name}): $e. Other entries for this method: ($es)") - case Nil => - _frontend.logger.trace(s"Storing new entry in cache for method (${m.name}) FAILED.") + if (cacheable_errors.isDefined) { + ViperCache.update(backendName, file, m, transformed_prog, cacheable_errors.get) match { + case e :: es => + _frontend.logger.trace(s"Storing new entry in cache for method (${m.name}): $e. Other entries for this method: ($es)") + case Nil => + _frontend.logger.trace(s"Storing new entry in cache for method (${m.name}) FAILED.") + } } } - }) + } else { + _frontend.logger.debug(s"Inconsistent error splitting; no cache update for this verification attempt in $file.") + } // combine errors: if (all_cached_errors.nonEmpty) { diff --git a/src/main/scala/viper/server/core/ViperCache.scala b/src/main/scala/viper/server/core/ViperCache.scala index 41084fa..7241e1f 100644 --- a/src/main/scala/viper/server/core/ViperCache.scala +++ b/src/main/scala/viper/server/core/ViperCache.scala @@ -314,7 +314,7 @@ object ViperCacheHelper { * * The second argument list is used for specifying external keys as (backend, file). * This is needed for removing separate parts of the hash table. - * + * * @see [[forgetFile]]. */ @tailrec From b3917c2a4c863a852aff1cfa30bcfe07e7a5597e Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 15:45:13 +0100 Subject: [PATCH 50/79] Update scala.yml Update Java in build script to version 15 --- .github/workflows/scala.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 1165887..31b1f83 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -54,10 +54,11 @@ jobs: - name: Install Z3 run: sudo apt-get update -y; sudo apt-get install -y z3 - - name: Set up JDK 1.8 + - name: Set up JDK 15 uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: '15.0.1' + architecture: x64 - name: Install SBT run: echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list; curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add; sudo apt-get update; sudo apt-get install sbt From 03a21f30e5fd7a8d3d03ac294577de3440bcd20b Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 16:52:38 +0100 Subject: [PATCH 51/79] Adapted JSON writers in ViperIDEProtocol after recent changes in trait ModelEntry --- .../http/jsonWriters/ViperIDEProtocol.scala | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala index 32f1a7f..0892f9f 100644 --- a/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala +++ b/src/main/scala/viper/server/frontends/http/jsonWriters/ViperIDEProtocol.scala @@ -16,7 +16,7 @@ import viper.server.vsi.{AstJobId, VerJobId} import viper.silicon.SymbLog import viper.silicon.state.terms.Term import viper.silver.reporter._ -import viper.silver.verifier._ +import viper.silver.verifier.{ValueEntry, _} object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport with DefaultJsonProtocol { @@ -102,20 +102,53 @@ object ViperIDEProtocol extends akka.http.scaladsl.marshallers.sprayjson.SprayJs } }) - implicit val modelEntry_writer: RootJsonFormat[ModelEntry] = lift(new RootJsonWriter[ModelEntry] { - override def write(obj: ModelEntry): JsValue = obj match { - case SingleEntry(value: String) => - JsString(value) - case MapEntry(options, els) => + implicit val constantEntry_writer: RootJsonFormat[ConstantEntry] = lift(new RootJsonWriter[ConstantEntry] { + override def write(obj: ConstantEntry): JsValue = JsString(obj.value) + }) + + implicit val applicationEntry_writer: RootJsonFormat[ApplicationEntry] = lift(new RootJsonWriter[ApplicationEntry] { + override def write(obj: ApplicationEntry): JsValue = JsObject( + "name" -> JsString(obj.name), + "args" -> JsArray(obj.arguments.map(_.toJson).toVector) + ) + }) + + implicit val modelValue_writer: RootJsonFormat[ValueEntry] = lift(new RootJsonWriter[ValueEntry] { + override def write(obj: ValueEntry): JsValue = obj match { + case c: ConstantEntry => + JsObject( + "type" -> JsString("constant_entry"), + "value" -> c.toJson + ) + case a: ApplicationEntry => JsObject( - "cases" -> JsObject(options.map { - case (args: Seq[String], res: String) => - (res, JsArray(args.map(_.toJson).toVector)) }), - "else" -> JsString(els) + "type" -> JsString("application_entry"), + "value" -> a.toJson ) } }) + implicit val mapEntry_writer: RootJsonFormat[MapEntry] = lift(new RootJsonWriter[MapEntry] { + override def write(obj: MapEntry): JsValue = JsObject( + "type" -> JsString("map_entry"), + "cases" -> JsArray(obj.options.map { + case (args: Seq[ValueEntry], res: ValueEntry) => + JsObject("args" -> JsArray(args.map(_.toJson).toVector), + "value" -> res.toJson) + }.toVector), + "default" -> obj.default.toJson + ) + }) + + implicit val modelEntry_writer: RootJsonFormat[ModelEntry] = lift(new RootJsonWriter[ModelEntry] { + override def write(obj: ModelEntry): JsValue = obj match { + case ve: ValueEntry => + ve.toJson + case me: MapEntry => + me.toJson + } + }) + implicit val model_writer: RootJsonFormat[Model] = lift(new RootJsonWriter[Model] { override def write(obj: Model): JsValue = JsObject(obj.entries.map { case (k: String, v: ModelEntry) => (k, v.toJson) }) From 3cd96c2077dcf0671c9fbb916fbe3042ad55400f Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 17:06:30 +0100 Subject: [PATCH 52/79] Update scala.yml Use pavpanchekha/setup-z3 to get the right version of Z3. --- .github/workflows/scala.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 31b1f83..1a585bd 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -51,8 +51,11 @@ jobs: - name: Symbolically link Silicon to ViperServer run: cd viperServer; ln --symbolic ../silicon - - name: Install Z3 - run: sudo apt-get update -y; sudo apt-get install -y z3 + - name: Install Z3-4.8.6 + uses: pavpanchekha/setup-z3 + with: + version: 4.8.6 + architecture: x64 - name: Set up JDK 15 uses: actions/setup-java@v1 From 8599edaa4f8b560027228b33e40b891cc6a72291 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 17:07:58 +0100 Subject: [PATCH 53/79] Update scala.yml --- .github/workflows/scala.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 1a585bd..d17916b 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -51,7 +51,7 @@ jobs: - name: Symbolically link Silicon to ViperServer run: cd viperServer; ln --symbolic ../silicon - - name: Install Z3-4.8.6 + - name: Install Z3-4.8.6@v1 uses: pavpanchekha/setup-z3 with: version: 4.8.6 From 173187ca77118aa031c09472be5435530786290d Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 17:08:44 +0100 Subject: [PATCH 54/79] Update scala.yml --- .github/workflows/scala.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index d17916b..d7de34c 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -51,8 +51,8 @@ jobs: - name: Symbolically link Silicon to ViperServer run: cd viperServer; ln --symbolic ../silicon - - name: Install Z3-4.8.6@v1 - uses: pavpanchekha/setup-z3 + - name: Install Z3-4.8.6 + uses: pavpanchekha/setup-z3@v1 with: version: 4.8.6 architecture: x64 From ec2cbfce75a295953dd3f342f2190ff935d1895f Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 17:54:47 +0100 Subject: [PATCH 55/79] Update scala.yml Try to fix the Z3 setup --- .github/workflows/scala.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index d7de34c..bf9f0fe 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -6,7 +6,7 @@ on: jobs: check-headers: - runs-on: ubuntu-latest + runs-on: ubuntu-16.04 steps: - name: Checkout ViperServer repo uses: actions/checkout@v2 @@ -51,11 +51,11 @@ jobs: - name: Symbolically link Silicon to ViperServer run: cd viperServer; ln --symbolic ../silicon - - name: Install Z3-4.8.6 - uses: pavpanchekha/setup-z3@v1 - with: - version: 4.8.6 - architecture: x64 + - name: Install Z3 + run: curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip && unzip z3.zip && sudo ln -s $(pwd)/$Z3/bin/z3 /usr/bin/z3 + env: + Z3_VERSION: "4.8.9" + Z3: "z3-$Z3_VERSION-x64-ubuntu-16.04" - name: Set up JDK 15 uses: actions/setup-java@v1 From fef997dcb320b22f133ff31d86ba933d40115719 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 18:04:09 +0100 Subject: [PATCH 56/79] Update scala.yml --- .github/workflows/scala.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index bf9f0fe..115084f 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -8,7 +8,7 @@ jobs: check-headers: runs-on: ubuntu-16.04 steps: - - name: Checkout ViperServer repo + - name: Checkouz3t ViperServer repo uses: actions/checkout@v2 - name: Check license headers uses: viperproject/check-license-header@v1 @@ -52,10 +52,12 @@ jobs: run: cd viperServer; ln --symbolic ../silicon - name: Install Z3 - run: curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip && unzip z3.zip && sudo ln -s $(pwd)/$Z3/bin/z3 /usr/bin/z3 + run: echo "Installing Z3 via command $Z3_INSTALLATION_COMMAND" + $Z3_INSTALLATION_COMMAND env: Z3_VERSION: "4.8.9" Z3: "z3-$Z3_VERSION-x64-ubuntu-16.04" + Z3_INSTALLATION_COMMAND: "curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip && unzip z3.zip && ln -s $Z3/bin/z3" - name: Set up JDK 15 uses: actions/setup-java@v1 From 239d34e853940edb55cc11632a97290607ad8ba5 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 18:13:53 +0100 Subject: [PATCH 57/79] Update scala.yml --- .github/workflows/scala.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 115084f..9b26ebe 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -8,7 +8,7 @@ jobs: check-headers: runs-on: ubuntu-16.04 steps: - - name: Checkouz3t ViperServer repo + - name: Checkout ViperServer repo uses: actions/checkout@v2 - name: Check license headers uses: viperproject/check-license-header@v1 @@ -52,12 +52,15 @@ jobs: run: cd viperServer; ln --symbolic ../silicon - name: Install Z3 - run: echo "Installing Z3 via command $Z3_INSTALLATION_COMMAND" - $Z3_INSTALLATION_COMMAND env: Z3_VERSION: "4.8.9" - Z3: "z3-$Z3_VERSION-x64-ubuntu-16.04" - Z3_INSTALLATION_COMMAND: "curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip && unzip z3.zip && ln -s $Z3/bin/z3" + Z3_PLATFORM: x64 + Z3_OS: ubuntu-16.04 + run: | + export Z3="z3-$Z3_VERSION-$Z3_PLATFORM-$Z3-OS" + curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip + unzip z3.zip + ln -s $Z3/bin/z3 - name: Set up JDK 15 uses: actions/setup-java@v1 From bcd99244d7543b279ce2c9e7e1f117e814ee471b Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 18:20:40 +0100 Subject: [PATCH 58/79] Update scala.yml --- .github/workflows/scala.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 9b26ebe..68f509d 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -56,8 +56,7 @@ jobs: Z3_VERSION: "4.8.9" Z3_PLATFORM: x64 Z3_OS: ubuntu-16.04 - run: | - export Z3="z3-$Z3_VERSION-$Z3_PLATFORM-$Z3-OS" + run: export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3-OS curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip unzip z3.zip ln -s $Z3/bin/z3 From 92f361e6d5895e0ee4179833856d250446555bcd Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 18:22:47 +0100 Subject: [PATCH 59/79] Update scala.yml --- .github/workflows/scala.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 68f509d..f6db98a 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -56,9 +56,9 @@ jobs: Z3_VERSION: "4.8.9" Z3_PLATFORM: x64 Z3_OS: ubuntu-16.04 - run: export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3-OS - curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip - unzip z3.zip + run: export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3-OS; + curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip; + unzip z3.zip; ln -s $Z3/bin/z3 - name: Set up JDK 15 From bbe993977be49540bca43ec49ffdb19a1e233e09 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 18:34:46 +0100 Subject: [PATCH 60/79] Update scala.yml --- .github/workflows/scala.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index f6db98a..dd3d57b 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -51,16 +51,26 @@ jobs: - name: Symbolically link Silicon to ViperServer run: cd viperServer; ln --symbolic ../silicon + - name: Print a greeting + env: + MY_VAR: Hi there! My name is + FIRST_NAME: Mona + MIDDLE_NAME: The + LAST_NAME: Octocat + run: | + echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME. + - name: Install Z3 env: - Z3_VERSION: "4.8.9" + Z3_VERSION: 4.8.9 Z3_PLATFORM: x64 Z3_OS: ubuntu-16.04 - run: export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3-OS; - curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip; - unzip z3.zip; - ln -s $Z3/bin/z3 - + run: | + export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3_OS; + curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip; + unzip z3.zip; + ln -s $Z3/bin/z3 + - name: Set up JDK 15 uses: actions/setup-java@v1 with: From cc8f3463c043bbc370a37eb8851419f993dc4a65 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 18:44:01 +0100 Subject: [PATCH 61/79] Update scala.yml --- .github/workflows/scala.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index dd3d57b..f00fcd8 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -51,15 +51,6 @@ jobs: - name: Symbolically link Silicon to ViperServer run: cd viperServer; ln --symbolic ../silicon - - name: Print a greeting - env: - MY_VAR: Hi there! My name is - FIRST_NAME: Mona - MIDDLE_NAME: The - LAST_NAME: Octocat - run: | - echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME. - - name: Install Z3 env: Z3_VERSION: 4.8.9 @@ -69,7 +60,7 @@ jobs: export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3_OS; curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip; unzip z3.zip; - ln -s $Z3/bin/z3 + sudo ln -s $(pwd)/$Z3/bin/z3 /usr/bin/z3 - name: Set up JDK 15 uses: actions/setup-java@v1 From b4e04832b923b38316243bba388fb0d28aedabf8 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 19:09:26 +0100 Subject: [PATCH 62/79] Update scala.yml No need to run sbt assembly for testing. --- .github/workflows/scala.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index f00fcd8..cc51bb2 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -18,7 +18,7 @@ jobs: strict: true build: - runs-on: ubuntu-latest + runs-on: ubuntu-16.04 steps: - name: Checkout ViperServer uses: actions/checkout@v2 @@ -70,9 +70,6 @@ jobs: - name: Install SBT run: echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list; curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add; sudo apt-get update; sudo apt-get install sbt - - - name: Assemble project - run: cd viperServer; sbt assembly - name: test project run: cd viperServer; export Z3_EXE="/usr/bin/z3"; env > print_dir.txt; sbt test From 26a3968c6071ad3efc3234c0927179e641f624df Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 19:21:45 +0100 Subject: [PATCH 63/79] Try to add nightly artifact publishing --- .github/workflows/scala.yml | 116 ++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 51 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index cc51bb2..6a8b5af 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -17,60 +17,74 @@ jobs: config: ./.github/license-check/config.json strict: true - build: + test: runs-on: ubuntu-16.04 steps: - - name: Checkout ViperServer - uses: actions/checkout@v2 - with: - path: viperServer - - name: Checkout Silver - uses: actions/checkout@v2 - with: - repository: viperproject/silver - path: silver - - name: Checkout Silicon - uses: actions/checkout@v2 - with: - repository: viperproject/silicon - path: silicon - - name: Checkout Carbon - uses: actions/checkout@v2 - with: - repository: viperproject/carbon - path: carbon - - - name: Symbolically link Silver to Carbon - run: cd carbon; ln --symbolic ../silver - - name: Symbolically link Silver to Silicon - run: cd silicon; ln --symbolic ../silver - - name: Symbolically link Silver to ViperServer - run: cd viperServer; ln --symbolic ../silver - - name: Symbolically link Carbon to ViperServer - run: cd viperServer; ln --symbolic ../carbon - - name: Symbolically link Silicon to ViperServer - run: cd viperServer; ln --symbolic ../silicon + - name: Checkout ViperServer + uses: actions/checkout@v2 + with: + path: viperServer + - name: Checkout Silver + uses: actions/checkout@v2 + with: + repository: viperproject/silver + path: silver + - name: Checkout Silicon + uses: actions/checkout@v2 + with: + repository: viperproject/silicon + path: silicon + - name: Checkout Carbon + uses: actions/checkout@v2 + with: + repository: viperproject/carbon + path: carbon - - name: Install Z3 - env: - Z3_VERSION: 4.8.9 - Z3_PLATFORM: x64 - Z3_OS: ubuntu-16.04 - run: | - export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3_OS; - curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip; - unzip z3.zip; - sudo ln -s $(pwd)/$Z3/bin/z3 /usr/bin/z3 + - name: Symbolically link Silver to Carbon + run: cd carbon; ln --symbolic ../silver + - name: Symbolically link Silver to Silicon + run: cd silicon; ln --symbolic ../silver + - name: Symbolically link Silver to ViperServer + run: cd viperServer; ln --symbolic ../silver + - name: Symbolically link Carbon to ViperServer + run: cd viperServer; ln --symbolic ../carbon + - name: Symbolically link Silicon to ViperServer + run: cd viperServer; ln --symbolic ../silicon + + - name: Install Z3 + env: + Z3_VERSION: 4.8.9 + Z3_PLATFORM: x64 + Z3_OS: ubuntu-16.04 + run: | + export Z3=z3-$Z3_VERSION-$Z3_PLATFORM-$Z3_OS; + curl -J -L https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION/$Z3.zip > z3.zip; + unzip z3.zip; + sudo ln -s $(pwd)/$Z3/bin/z3 /usr/bin/z3 + + - name: Set up JDK 15 + uses: actions/setup-java@v1 + with: + java-version: '15.0.1' + architecture: x64 - - name: Set up JDK 15 - uses: actions/setup-java@v1 - with: - java-version: '15.0.1' - architecture: x64 + - name: Install SBT + run: | + echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list; + curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add; + sudo apt-get update; sudo apt-get install sbt - - name: Install SBT - run: echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list; curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add; sudo apt-get update; sudo apt-get install sbt - - - name: test project - run: cd viperServer; export Z3_EXE="/usr/bin/z3"; env > print_dir.txt; sbt test + - name: Test Project + run: | + cd viperServer; + export Z3_EXE="/usr/bin/z3"; + env > print_dir.txt; + sbt test + - name: Publish Nightly Artifact + uses: skx/github-action-publish-binaries@release-1.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + releaseId: ${{ needs.create_release.outputs.id }} + args: 'target/scala-*/viper.jar' From fd382c4fa5dd4434312c0774514b51420eb0fc5c Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 19:23:29 +0100 Subject: [PATCH 64/79] Update scala.yml --- .github/workflows/scala.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 6a8b5af..71abe08 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -80,6 +80,11 @@ jobs: export Z3_EXE="/usr/bin/z3"; env > print_dir.txt; sbt test + + - name: Assemble the fat JAR file + run: | + cd viperServer; + sbt assembly - name: Publish Nightly Artifact uses: skx/github-action-publish-binaries@release-1.3 From 7125dcd0cb446c8eb9df1b6ef42f2a483474b4de Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 19:33:01 +0100 Subject: [PATCH 65/79] Temporary config --- .github/workflows/scala.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 71abe08..d6cf4b0 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -73,13 +73,6 @@ jobs: echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list; curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add; sudo apt-get update; sudo apt-get install sbt - - - name: Test Project - run: | - cd viperServer; - export Z3_EXE="/usr/bin/z3"; - env > print_dir.txt; - sbt test - name: Assemble the fat JAR file run: | From 46f715d23235b8158292d285c73c96588dac12db Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Tue, 5 Jan 2021 19:47:39 +0100 Subject: [PATCH 66/79] Update scala.yml --- .github/workflows/scala.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index d6cf4b0..7cd70f6 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -80,7 +80,7 @@ jobs: sbt assembly - name: Publish Nightly Artifact - uses: skx/github-action-publish-binaries@release-1.3 + uses: skx/github-action-publish-binaries@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From 58570c5d2ae388e5225d10ab034f215e79f1a7a3 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:08:13 +0100 Subject: [PATCH 67/79] Update scala.yml --- .github/workflows/scala.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 7cd70f6..0611515 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -20,6 +20,10 @@ jobs: test: runs-on: ubuntu-16.04 steps: + - name: Get Current Date + id: date + run: echo "name=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + - name: Checkout ViperServer uses: actions/checkout@v2 with: @@ -80,9 +84,14 @@ jobs: sbt assembly - name: Publish Nightly Artifact - uses: skx/github-action-publish-binaries@master env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - releaseId: ${{ needs.create_release.outputs.id }} - args: 'target/scala-*/viper.jar' + FILE: target/scala-2.13/viper.jar + NIGHTLY_RELEASE_ID: 36021300 + ARTIFACT_NAME: viper-nightly-${{ steps.date.outputs.date }}.jar + run: | + echo Publishing $FILE as $ARTIFACT_NAME ...; + curl -H "Accept: application/vnd.github.v3+json" + -H "Content-Type: $(file -b --mime-type $FILE)" + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" + --data-binary @$FILE + "https://uploads.github.com/repos/aterga/viperserver/releases/$NIGHTLY_RELEASE_ID/assets?name=$ARTIFACT_NAME" From 16f734e9eb3b5d345926cc967035692f65964141 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:21:39 +0100 Subject: [PATCH 68/79] Update release.yml --- .github/workflows/release.yml | 39 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index adfae72..2c9f86d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,28 @@ +name: Scala CI + on: - push: - types: [created] -name: Handle Push + pull_request: + jobs: - generate: - name: Create nigtly artifacts - runs-on: ubuntu-latest + nightly: + runs-on: ubuntu-16.04 steps: - - name: Checkout the repository - uses: actions/checkout@master - - name: Generate the artifacts - uses: skx/github-action-build@master - - name: Upload the artifacts - uses: skx/github-action-publish-binaries@master + - name: Get Current Date + id: date + run: echo "::set-output name=DATE::$(date +'%Y-%m-%d')" + + - name: Generate atrifact + run: mkdir -p target/scala-2.13; cat "Hello Viper" > target/scala-2.13/viper.jar + + - name: Publish Nightly Artifact env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: 'example-*' + FILE: target/scala-2.13/viper.jar + NIGHTLY_RELEASE_ID: 36021300 + ARTIFACT_NAME: viper-nightly-${{ steps.date.outputs.DATE }}.jar + run: | + echo Publishing $FILE as $ARTIFACT_NAME ...; + curl -H "Accept: application/vnd.github.v3+json" \ + -H "Content-Type: $(file -b --mime-type $FILE)" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + --data-binary @$FILE \ + "https://uploads.github.com/repos/aterga/viperserver/releases/$NIGHTLY_RELEASE_ID/assets?name=$ARTIFACT_NAME" From 2cf1395fc73533ca5c19f5224c5a594aa0463ba0 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:25:40 +0100 Subject: [PATCH 69/79] Update release.yml --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c9f86d..58efc54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,7 @@ name: Scala CI on: pull_request: + workflow_dispatch: jobs: nightly: From 0e48c415493e6c8fb90981b5671a6453afd5bdf9 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:27:51 +0100 Subject: [PATCH 70/79] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58efc54..8b8017e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Scala CI +name: Release on: pull_request: From 9f3e18e4deaa3069ce460d4458d47752994cdd79 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:29:00 +0100 Subject: [PATCH 71/79] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b8017e..b5bdb43 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: run: echo "::set-output name=DATE::$(date +'%Y-%m-%d')" - name: Generate atrifact - run: mkdir -p target/scala-2.13; cat "Hello Viper" > target/scala-2.13/viper.jar + run: mkdir -p target/scala-2.13; echo "Hello Viper" > target/scala-2.13/viper.jar - name: Publish Nightly Artifact env: From 27d87aa4187d631c5239803f3d9814ec9bfee38e Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:36:40 +0100 Subject: [PATCH 72/79] Update release.yml --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5bdb43..0fd97fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,6 @@ -name: Release +name: Release Nightly (Under Construction) on: - pull_request: workflow_dispatch: jobs: From ca5da3668113a9aa8f7cc35d6d7dc1964f0c1bc4 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:36:55 +0100 Subject: [PATCH 73/79] Update scala.yml --- .github/workflows/scala.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 0611515..9a917e0 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -1,4 +1,4 @@ -name: Scala CI +name: Check, Build, Test, Publish Nightly on: push: @@ -22,7 +22,7 @@ jobs: steps: - name: Get Current Date id: date - run: echo "name=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + run: echo "::set-output name=DATE::$(date +'%Y-%m-%d')" - name: Checkout ViperServer uses: actions/checkout@v2 @@ -87,11 +87,11 @@ jobs: env: FILE: target/scala-2.13/viper.jar NIGHTLY_RELEASE_ID: 36021300 - ARTIFACT_NAME: viper-nightly-${{ steps.date.outputs.date }}.jar + ARTIFACT_NAME: viper-nightly-${{ steps.date.outputs.DATE }}.jar run: | echo Publishing $FILE as $ARTIFACT_NAME ...; - curl -H "Accept: application/vnd.github.v3+json" - -H "Content-Type: $(file -b --mime-type $FILE)" - -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" - --data-binary @$FILE + curl -H "Accept: application/vnd.github.v3+json" \ + -H "Content-Type: $(file -b --mime-type $FILE)" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + --data-binary @$FILE \ "https://uploads.github.com/repos/aterga/viperserver/releases/$NIGHTLY_RELEASE_ID/assets?name=$ARTIFACT_NAME" From 233587348536271d6f4b2dc99c3a9311b4d1a5de Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:43:19 +0100 Subject: [PATCH 74/79] Update scala.yml --- .github/workflows/scala.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 9a917e0..51e0221 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -85,7 +85,7 @@ jobs: - name: Publish Nightly Artifact env: - FILE: target/scala-2.13/viper.jar + FILE: viperServer/target/scala-2.13/viper.jar NIGHTLY_RELEASE_ID: 36021300 ARTIFACT_NAME: viper-nightly-${{ steps.date.outputs.DATE }}.jar run: | From 7c8113de6947c2d1c0b6781b18dcdb4633acc89e Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 00:51:07 +0100 Subject: [PATCH 75/79] Update scala.yml --- .github/workflows/scala.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index 51e0221..314257b 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -77,7 +77,14 @@ jobs: echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list; curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add; sudo apt-get update; sudo apt-get install sbt - + + - name: test project + run: | + cd viperServer; + export Z3_EXE="/usr/bin/z3"; + env > print_dir.txt; + sbt test + - name: Assemble the fat JAR file run: | cd viperServer; @@ -94,4 +101,4 @@ jobs: -H "Content-Type: $(file -b --mime-type $FILE)" \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ --data-binary @$FILE \ - "https://uploads.github.com/repos/aterga/viperserver/releases/$NIGHTLY_RELEASE_ID/assets?name=$ARTIFACT_NAME" + "https://uploads.github.com/repos/${{ github.repository }}/releases/$NIGHTLY_RELEASE_ID/assets?name=$ARTIFACT_NAME" From 172683f1fbd171c22e9b4ce38211c23adeae35d3 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 18:33:50 +0100 Subject: [PATCH 76/79] Delete release.yml --- .github/workflows/release.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 0fd97fe..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Release Nightly (Under Construction) - -on: - workflow_dispatch: - -jobs: - nightly: - runs-on: ubuntu-16.04 - steps: - - name: Get Current Date - id: date - run: echo "::set-output name=DATE::$(date +'%Y-%m-%d')" - - - name: Generate atrifact - run: mkdir -p target/scala-2.13; echo "Hello Viper" > target/scala-2.13/viper.jar - - - name: Publish Nightly Artifact - env: - FILE: target/scala-2.13/viper.jar - NIGHTLY_RELEASE_ID: 36021300 - ARTIFACT_NAME: viper-nightly-${{ steps.date.outputs.DATE }}.jar - run: | - echo Publishing $FILE as $ARTIFACT_NAME ...; - curl -H "Accept: application/vnd.github.v3+json" \ - -H "Content-Type: $(file -b --mime-type $FILE)" \ - -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - --data-binary @$FILE \ - "https://uploads.github.com/repos/aterga/viperserver/releases/$NIGHTLY_RELEASE_ID/assets?name=$ARTIFACT_NAME" From a6844207330d7e35445368f6590ca0644a0c26a7 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 22:12:03 +0100 Subject: [PATCH 77/79] Update build.sbt --- build.sbt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index ec3e47e..dca31a3 100644 --- a/build.sbt +++ b/build.sbt @@ -30,11 +30,11 @@ lazy val server = (project in file(".")) Test / fork := true, // Compilation settings - libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.5.23", - libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8", - libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.23", - libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % "2.5.23" % Test, - libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % "10.1.8" % Test, + libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.10", + libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.2.1", + libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.6.10", + libraryDependencies += "com.typesafe.akka" %% "akka-stream-testkit" % "2.6.10" % Test, + libraryDependencies += "com.typesafe.akka" %% "akka-http-testkit" % "10.2.1" % Test, libraryDependencies += "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.8.1", // Java implementation of language server protocol // Run settings From 891449e9efaf69b6bbe0185410d40873d88a4637 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 22:13:59 +0100 Subject: [PATCH 78/79] Update plugins.sbt --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 7d66638..d60a712 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ // http://creativecommons.org/publicdomain/zero/1.0/ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.12") +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.7.6") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") From b376991ae3f9978a4523a9738c0598bc80105a35 Mon Sep 17 00:00:00 2001 From: Arshavir Ter-Gabrielyan Date: Wed, 6 Jan 2021 22:15:57 +0100 Subject: [PATCH 79/79] Refactored port selection for web sockets. --- src/main/scala/viper/server/LanguageServerRunner.scala | 7 ++++--- src/main/scala/viper/server/ViperConfig.scala | 8 +------- src/main/scala/viper/server/core/ViperCoreServer.scala | 2 +- .../viper/server/frontends/http/ViperHttpServer.scala | 5 +++-- src/main/scala/viper/server/vsi/HTTP.scala | 5 +++++ 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/scala/viper/server/LanguageServerRunner.scala b/src/main/scala/viper/server/LanguageServerRunner.scala index 1f5b7fa..4bff53e 100644 --- a/src/main/scala/viper/server/LanguageServerRunner.scala +++ b/src/main/scala/viper/server/LanguageServerRunner.scala @@ -19,7 +19,7 @@ object LanguageServerRunner { def main(args: Array[String]): Unit = { _config = new ViperConfig(args) _config.verify() - val port = _config.port() + val port = _config.port.getOrElse(0) runServer(port) } @@ -28,11 +28,12 @@ object LanguageServerRunner { try { val socket = new Socket("localhost", port) val localAddress = socket.getLocalAddress.getHostAddress - println(s"going to listen on $localAddress:$port") - Coordinator.port = port + Coordinator.port = socket.getPort Coordinator.url = localAddress + println(s"preparing to listen on ${Coordinator.url}:${Coordinator.port}") + val server: CustomReceiver = new CustomReceiver() val launcher = Launcher.createLauncher(server, classOf[IdeLanguageClient], socket.getInputStream, socket.getOutputStream) server.connect(launcher.getRemoteProxy) diff --git a/src/main/scala/viper/server/ViperConfig.scala b/src/main/scala/viper/server/ViperConfig.scala index d05e03d..6271fdc 100644 --- a/src/main/scala/viper/server/ViperConfig.scala +++ b/src/main/scala/viper/server/ViperConfig.scala @@ -11,7 +11,6 @@ import java.io.File import org.rogach.scallop.{ScallopConf, ScallopOption, singleArgConverter} import viper.server.utility.Helpers.{canonizedFile, validatePath} import viper.server.utility.ibm -import viper.server.utility.ibm.Socket class ViperConfig(args: Seq[String]) extends ScallopConf(args) { @@ -55,13 +54,8 @@ class ViperConfig(args: Seq[String]) extends ScallopConf(args) { val port: ScallopOption[Int] = opt[Int]("port", 'p', descr = ("Specifies the port on which ViperServer will be started." - + s"The port must be an integer in range [${Socket.MIN_PORT_NUMBER}-${ibm.Socket.MAX_PORT_NUMBER}]" + + s"The port must be an integer in range [${ibm.Socket.MIN_PORT_NUMBER}-${ibm.Socket.MAX_PORT_NUMBER}]" + "If the option is omitted, an available port will be selected automatically."), - default = { - val p = ibm.Socket.findFreePort - println(s"Automatically selecting port $p ...") - Some(p) - }, validate = p => try { ibm.Socket.available(p) } catch { diff --git a/src/main/scala/viper/server/core/ViperCoreServer.scala b/src/main/scala/viper/server/core/ViperCoreServer.scala index 1b79376..646a636 100644 --- a/src/main/scala/viper/server/core/ViperCoreServer.scala +++ b/src/main/scala/viper/server/core/ViperCoreServer.scala @@ -43,7 +43,7 @@ class ViperCoreServer(val _args: Array[String]) extends VerificationServer with ViperCache.initialize(logger.get, config.backendSpecificCache()) super.start(config.maximumActiveJobs()) - println(s"ViperCoreServer started.") + println(s"ViperCoreServer has started.") } def requestAst(arg_list: List[String]): AstJobId = { diff --git a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala index 2021ece..7a750d3 100644 --- a/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala +++ b/src/main/scala/viper/server/frontends/http/ViperHttpServer.scala @@ -20,6 +20,7 @@ import viper.server.ViperConfig import viper.server.core.{AstConstructionFailureException, ViperBackendConfig, ViperCache, ViperCoreServer} import viper.server.frontends.http.jsonWriters.ViperIDEProtocol.{AlloyGenerationRequestComplete, AlloyGenerationRequestReject, CacheFlushAccept, CacheFlushReject, JobDiscardAccept, JobDiscardReject, ServerStopConfirmed, VerificationRequestAccept, VerificationRequestReject} import viper.server.utility.Helpers.{getArgListFromArgString, validateViperFile} +import viper.server.utility.ibm import viper.server.vsi.Requests.CacheResetRequest import viper.server.vsi._ import viper.silver.logger.ViperLogger @@ -39,9 +40,9 @@ class ViperHttpServer(_args: Array[String]) ViperCache.initialize(logger.get, config.backendSpecificCache()) - port = config.port() + port = config.port.getOrElse(ibm.Socket.findFreePort) super.start(config.maximumActiveJobs()) - println(s"ViperServer online at http://localhost:${config.port()}") + println(s"ViperServer online at http://localhost:$port}") } def setRoutes(): Route = { diff --git a/src/main/scala/viper/server/vsi/HTTP.scala b/src/main/scala/viper/server/vsi/HTTP.scala index c0c8366..e1b4ecb 100644 --- a/src/main/scala/viper/server/vsi/HTTP.scala +++ b/src/main/scala/viper/server/vsi/HTTP.scala @@ -26,6 +26,11 @@ import scala.util.{Failure, Success, Try} * */ sealed trait BasicHttp { + /** Specifies the port through which the clients may access this server instance. + * + * The default port number tells the system to automatically pick an available port + * (depends on the implementation of the underlying socket library) + * */ var port: Int = _ /** Must be implemented to return the routes defined for this server.