Skip to content

Commit 4b027cf

Browse files
authored
Merge pull request #99 from viperproject/support-for-plugins
Support for Plugins
2 parents 48aef35 + dc20e68 commit 4b027cf

12 files changed

Lines changed: 415 additions & 94 deletions

File tree

carbon

Submodule carbon updated 1 file

src/main/scala/viper/server/core/AstWorker.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import ch.qos.logback.classic.Logger
1010
import viper.server.utility.AstGenerator
1111
import viper.server.vsi.AstConstructionException
1212
import viper.silver.ast.Program
13-
import viper.silver.reporter.ExceptionReport
13+
import viper.silver.reporter.{Entity, ExceptionReport}
14+
import viper.silver.verifier.VerificationResult
1415

1516

1617
object ViperFileNotFoundException extends AstConstructionException
@@ -59,5 +60,10 @@ class AstWorker(val arg_list: List[String],
5960
ast_option
6061
}
6162

63+
override def mapEntityVerificationResult(entity: Entity, result: VerificationResult): VerificationResult = {
64+
logger.error(s"unexpected operation: AstWorker received an entity success or entity failure message, which should not occur while constructing a Viper AST")
65+
???
66+
}
67+
6268
override def call(): Option[Program] = constructAst()
6369
}

src/main/scala/viper/server/core/MessageReportingTask.scala

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ package viper.server.core
88

99
import ch.qos.logback.classic.Logger
1010
import viper.server.vsi.MessageStreamingTask
11-
import viper.silver.reporter.{EntityFailureMessage, EntitySuccessMessage, Message, Reporter}
11+
import viper.silver.reporter.{Entity, EntityFailureMessage, EntitySuccessMessage, Message, Reporter, Time, VerificationResultMessage}
12+
import viper.silver.verifier.{Success, VerificationResult}
1213

1314
trait MessageReportingTask[T] extends MessageStreamingTask[T] with ViperPost {
1415

@@ -23,6 +24,15 @@ trait MessageReportingTask[T] extends MessageStreamingTask[T] with ViperPost {
2324
super.registerTaskEnd(success, logger)
2425
}
2526

27+
/** implementations of this functions should let plugins map verification results for each entity */
28+
def mapEntityVerificationResult(entity: Entity, result: VerificationResult): VerificationResult
29+
30+
private def processEntityResultMessage(verifier: String, entity: Entity,
31+
verificationTime: Time, result: VerificationResult, cached: Boolean): Unit = {
32+
val mappedResult = mapEntityVerificationResult(entity, result)
33+
enqueueMessage(VerificationResultMessage(verifier, entity, verificationTime, mappedResult, cached))
34+
}
35+
2636
// Implementation of the Reporter interface used by the backend.
2737
class ActorReporter(tag: String) extends Reporter {
2838
val name = s"ViperServer_$tag"
@@ -35,6 +45,13 @@ trait MessageReportingTask[T] extends MessageStreamingTask[T] with ViperPost {
3545
// Do not re-send messages about AST nodes that have been cached;
3646
// the information about these nodes is going to be reported anyway.
3747

48+
// to properly support streaming of (partial) verification results, we must invoke
49+
// the plugins to do the post-processing and enqueue the resulting message:
50+
case EntitySuccessMessage(verifier, entity, verificationTime, cached) =>
51+
processEntityResultMessage(verifier, entity, verificationTime, Success, cached)
52+
case EntityFailureMessage(verifier, entity, verificationTime, failure, cached) =>
53+
processEntityResultMessage(verifier, entity, verificationTime, failure, cached)
54+
3855
case m =>
3956
enqueueMessage(m)
4057
}

src/main/scala/viper/server/core/VerificationWorker.scala

Lines changed: 146 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
44
//
5-
// Copyright (c) 2011-2020 ETH Zurich.
5+
// Copyright (c) 2011-2023 ETH Zurich.
66

77
package viper.server.core
88

@@ -11,7 +11,7 @@ import viper.carbon.CarbonFrontend
1111
import viper.server.vsi.Envelope
1212
import viper.silicon.{Silicon, SiliconFrontend}
1313
import viper.silver.ast._
14-
import viper.silver.frontend.{DefaultStates, SilFrontend}
14+
import viper.silver.frontend.SilFrontend
1515
import viper.silver.reporter.{Reporter, _}
1616
import viper.silver.verifier.{AbstractVerificationError, VerificationResult, _}
1717

@@ -83,28 +83,33 @@ class VerificationWorker(private val command: List[String],
8383
case _: java.nio.channels.ClosedByInterruptException =>
8484
case e: Throwable =>
8585
enqueueMessage(ExceptionReport(e))
86-
logger.trace(s"Creation/Execution of the verification backend " +
86+
val stackTraceElements = e.getStackTrace
87+
for (stackTrace <- stackTraceElements) {
88+
logger.error(stackTrace.getClassName + " " + stackTrace.getMethodName + " " + stackTrace.getLineNumber)
89+
}
90+
logger.error(s"Creation or execution of the verification backend " +
8791
s"${if (backend == null) "<null>" else backend.toString} resulted in an exception.", e)
88-
} finally {
89-
try {
90-
backend.stop()
91-
} catch {
92-
case e: Throwable =>
93-
logger.trace(s"Stopping the verification backend ${if (backend == null) "<null>" else backend.toString} resulted in exception.", e)
94-
}
9592
}
9693
if (success) {
9794
logger.info(s"The command `${command.mkString(" ")}` has been executed.")
9895
registerTaskEnd(true)
9996
} else {
100-
logger.error(s"The command `${command.mkString(" ")}` did not result in initialization of verification backend.")
97+
logger.error(s"The command `${command.mkString(" ")}` resulted in an exception.")
10198
registerTaskEnd(false)
10299
}
103100
}
104101

102+
override def mapEntityVerificationResult(entity: Entity, result: VerificationResult): VerificationResult = {
103+
backend.mapEntityVerificationResult(entity, result)
104+
}
105+
105106
override def call(): Unit = run()
106107
}
107108

109+
/**
110+
* ViperBackend that verifies `_ast` using `_frontend` when calling `execute`.
111+
* Note that we wrap `_frontend` in this way to support custom backends (which are instances of `SilFrontend`).
112+
*/
108113
class ViperBackend(val backendName: String, private val _frontend: SilFrontend, private val programId: String, private val _ast: Program) {
109114

110115
override def toString: String = {
@@ -114,47 +119,90 @@ class ViperBackend(val backendName: String, private val _frontend: SilFrontend,
114119
s"ViperBackend( ${_frontend.verifier.name} )"
115120
}
116121

117-
/** Run the backend verification functionality
118-
* */
122+
/** Run the backend verification functionality */
119123
def execute(args: Seq[String]): Unit = {
124+
initialize(args)
125+
126+
/**
127+
* the architecture is as follows:
128+
* First, plugins can transform the AST (via their `beforeVerify` methods).
129+
* Then, caching operates on the AST that would be sent to the verifier for verification. Caching turns methods
130+
* for which a cache hit exists into abstract methods. The resulting AST is then sent to the verifier.
131+
* Afterwards, the caching postprocessing happens: The verification results reported by the verifier are added to
132+
* the cache and are combined with the verification results obtained from the cache for cached methods.
133+
* As the last step, the combined verification results are handed to the plugins (as `mapVerificationResult`) to
134+
* back-translate the errors according to the transformation they performed in `beforeVerify`.
135+
*
136+
* Caching is thus transparent to clients of ViperServer and in particular plugins. Additionally, plugins creating
137+
* multiple methods benefit from partial caching: Imagine a termination plugin that creates additional methods to
138+
* check termination. For small modifications to the input programs, a method might have changed (and thus lead to
139+
* a cache miss) but as long long as the method checking for termination stays the same, the termination check might
140+
* still be cached.
141+
*
142+
* Note however that the `cached` flag for verification errors is best effort: Since caching is (almost) transparent
143+
* to plugins, it might sometimes not be possible for a plugin to correctly set the cache flag. E.g., the refute
144+
* plugin can see the cached flag for assertion failures when mapping the verification results. Since it's not
145+
* aware which errors belong to which member, the plugin cannot set the `cached` flag for refute errors, i.e.
146+
* verification errors that were expected but did not occur.
147+
*/
148+
val overallResult = try {
149+
val res = for {
150+
filteredProgram <- filter(_ast)
151+
innerProgram <- beforeVerify(filteredProgram)
152+
cachingResult = caching(innerProgram)
153+
verificationResult <- verification(cachingResult.transformedProgram)
154+
combinedVerificationResult = postCaching(cachingResult, verificationResult)
155+
mappedVerificationResult = mapVerificationResult(innerProgram, combinedVerificationResult)
156+
} yield mappedVerificationResult
157+
turnEitherIntoVerificationResult(res)
158+
} finally {
159+
stop()
160+
}
161+
162+
finish(overallResult)
163+
}
164+
165+
def mapEntityVerificationResult(entity: Entity, verificationResult: VerificationResult): VerificationResult = {
166+
_frontend.plugins.mapEntityVerificationResult(entity, verificationResult)
167+
}
168+
169+
/** initializes frontend such that the associated verifier is ready for verification */
170+
private def initialize(args: Seq[String]): Unit = {
120171
// --ignoreFile is not enough as Silicon still tries to parse the provided filepath unless
121172
// the following dummy file is used instead (see Silicon issue #552):
122173
val argsWithDummyFilename = args ++ Seq("--ignoreFile", Silicon.dummyInputFilename)
123174
_frontend.setStartTime()
124175
_frontend.setVerifier( _frontend.createVerifier(argsWithDummyFilename.mkString(" ")) )
125176

126-
if (!_frontend.prepare(argsWithDummyFilename)) return
127-
_frontend.init( _frontend.verifier )
128-
129-
val temp_result: Option[VerificationResult] = if (_frontend.config.disableCaching()) {
130-
_frontend.logger.info("Verification with caching disabled")
131-
Some(_frontend.verifier.verify(_ast))
132-
} else {
133-
_frontend.logger.info("Verification with caching enabled")
134-
doCachedVerification(_ast)
135-
_frontend.getVerificationResult
177+
if (!_frontend.prepare(argsWithDummyFilename)) {
178+
return
136179
}
180+
_frontend.init( _frontend.verifier )
181+
}
137182

138-
temp_result match {
139-
case Some(Success) =>
140-
_frontend.logger.debug(s"Verification successful (members: ${_ast.members.map(_.name).mkString(", ")})")
141-
_frontend.reporter report OverallSuccessMessage(_frontend.getVerifierName, System.currentTimeMillis() - _frontend.startTime)
142-
// TODO: Think again about where to detect and trigger SymbExLogging
143-
case Some(f@Failure(_)) =>
144-
// Cached errors will be reported as soon as they are retrieved from the cache.
145-
_frontend.logger.debug(s"Verification has failed (errors: ${f.errors.map(_.readableMessage).mkString("\n")}; members: ${_ast.members.map(_.name).mkString(", ")})")
146-
_frontend.reporter report OverallFailureMessage(_frontend.getVerifierName,
147-
System.currentTimeMillis() - _frontend.startTime,
148-
Failure(f.errors))
149-
case None =>
183+
private def filter(input: Program): Either[Seq[AbstractError], Program] = {
184+
_frontend.plugins.beforeMethodFilter(input) match {
185+
case Some(inputPlugin) =>
186+
// Filter methods according to command-line arguments.
187+
val verifyMethods =
188+
if (_frontend.config != null && _frontend.config.methods() != ":all") Seq("methods", _frontend.config.methods())
189+
else inputPlugin.methods map (_.name)
190+
val methods = inputPlugin.methods filter (m => verifyMethods.contains(m.name))
191+
Right(Program(inputPlugin.domains, inputPlugin.fields, inputPlugin.functions, inputPlugin.predicates, methods, inputPlugin.extensions)(inputPlugin.pos, inputPlugin.info, inputPlugin.errT))
192+
193+
case None => Left(_frontend.plugins.errors)
150194
}
151195
}
152196

153-
private def doCachedVerification(real_program: Program): Unit = {
154-
/** Top level branch is here for the same reason as in
155-
* {{{viper.silver.frontend.DefaultFrontend.verification()}}} */
197+
case class CachingResult(transformedProgram: Program, cachedErrors: Seq[VerificationError])
156198

157-
val (transformed_prog, cached_results) = ViperCache.applyCache(backendName, programId, real_program)
199+
private def caching(input: Program): CachingResult = {
200+
if (_frontend.config.disableCaching()) {
201+
// NOP
202+
return CachingResult(input, Seq.empty)
203+
}
204+
205+
val (transformed_prog, cached_results) = ViperCache.applyCache(backendName, programId, input)
158206

159207
// collect and report errors
160208
val all_cached_errors: collection.mutable.ListBuffer[VerificationError] = ListBuffer()
@@ -171,23 +219,41 @@ class ViperBackend(val backendName: String, private val _frontend: SilFrontend,
171219
})
172220

173221
val methodsToVerify: Seq[Method] = transformed_prog.methods.filter(_.body.isDefined)
174-
175222
_frontend.logger.debug(
176223
s"Retrieved data from cache..." +
177224
s" cachedErrors: ${all_cached_errors.map(_.loggableMessage)};" +
178225
s" cachedMethods: ${cached_results.map(_.method.name)};" +
179226
s" methodsToVerify: ${methodsToVerify.map(_.name)}.")
180227
_frontend.logger.trace(s"The cached program is equivalent to: \n${transformed_prog.toString()}")
181228

182-
val ver_result: VerificationResult = _frontend.verifier.verify(transformed_prog)
183-
_frontend.setVerificationResult(ver_result)
184-
_frontend.setState(DefaultStates.Verification)
229+
CachingResult(transformed_prog, all_cached_errors.toSeq)
230+
}
231+
232+
private def beforeVerify(input: Program): Either[Seq[AbstractError], Program] =
233+
_frontend.plugins.beforeVerify(input) match {
234+
case Some(programPlugin) => Right(programPlugin)
235+
case None => Left(_frontend.plugins.errors)
236+
}
237+
238+
private def verification(input: Program): Either[Seq[AbstractError], VerificationResult] =
239+
Right(_frontend.verifier.verify(input))
240+
241+
private def mapVerificationResult(input: Program, result: VerificationResult): VerificationResult =
242+
_frontend.plugins.mapVerificationResult(input, result)
243+
244+
private def postCaching(cachingResult: CachingResult, verificationResult: VerificationResult): VerificationResult = {
245+
if (_frontend.config.disableCaching()) {
246+
// NOP
247+
return verificationResult
248+
}
249+
250+
val astAfterApplyingCache = cachingResult.transformedProgram
251+
val methodsToVerify: Seq[Method] = astAfterApplyingCache.methods.filter(_.body.isDefined)
185252

186253
val meth_to_err_map: Seq[(Method, Option[List[AbstractVerificationError]])] = methodsToVerify.map((m: Method) => {
187254
// Results come back irrespective of program Member.
188255
val cacheable_errors: Option[List[AbstractVerificationError]] = for {
189-
verRes <- _frontend.getVerificationResult
190-
cache_errs <- verRes match {
256+
cache_errs <- verificationResult match {
191257
case Failure(errs) =>
192258
val r = getMethodSpecificErrors(m, errs)
193259
_frontend.logger.debug(s"getMethodSpecificErrors returned $r")
@@ -204,7 +270,7 @@ class ViperBackend(val backendName: String, private val _frontend: SilFrontend,
204270
// (otherwise it is unsafe to cache the results)
205271
val update_cache_criterion: Boolean = {
206272
val all_errors_in_file = meth_to_err_map.flatMap(_._2).flatten
207-
_frontend.getVerificationResult.get match {
273+
verificationResult match {
208274
case Success =>
209275
all_errors_in_file.isEmpty
210276
case Failure(errors) =>
@@ -219,7 +285,7 @@ class ViperBackend(val backendName: String, private val _frontend: SilFrontend,
219285
_frontend.logger.debug(s"Obtained cacheable errors: $cacheable_errors")
220286

221287
if (cacheable_errors.isDefined) {
222-
ViperCache.update(backendName, programId, m, transformed_prog, cacheable_errors.get) match {
288+
ViperCache.update(backendName, programId, m, astAfterApplyingCache, cacheable_errors.get) match {
223289
case e :: es =>
224290
_frontend.logger.trace(s"Storing new entry in cache for method '${m.name}' and backend '$backendName': $e. Other entries for this method: ($es)")
225291
case Nil =>
@@ -235,16 +301,38 @@ class ViperBackend(val backendName: String, private val _frontend: SilFrontend,
235301
ViperCache.writeToFile()
236302

237303
// combine errors:
238-
if (all_cached_errors.nonEmpty) {
239-
_frontend.getVerificationResult.get match {
304+
if (cachingResult.cachedErrors.isEmpty) {
305+
verificationResult // verificationResult remains unchanged as the cached errors (which are none) do not change the outcome
306+
} else {
307+
verificationResult match {
240308
case Failure(errorList) =>
241-
_frontend.setVerificationResult(Failure(errorList ++ all_cached_errors))
309+
Failure(errorList ++ cachingResult.cachedErrors)
242310
case Success =>
243-
_frontend.setVerificationResult(Failure(all_cached_errors.toSeq))
311+
Failure(cachingResult.cachedErrors)
244312
}
245313
}
246314
}
247315

316+
/**
317+
* LA Jan 5 2023: unclear whether this is necessary at all. SilFrontend neither calls start nor stop on the verifier and ViperBackend used to call only stop in the past.
318+
*/
319+
private def stop(): Unit = {
320+
_frontend.verifier.stop()
321+
}
322+
323+
private def finish(verificationResult: VerificationResult): Unit = {
324+
_frontend.plugins.beforeFinish(verificationResult) match {
325+
case Success =>
326+
_frontend.logger.debug(s"Verification successful (members: ${_ast.members.map(_.name).mkString(", ")})")
327+
_frontend.reporter report OverallSuccessMessage(_frontend.getVerifierName, System.currentTimeMillis() - _frontend.startTime)
328+
// TODO: Think again about where to detect and trigger SymbExLogging
329+
case f: Failure =>
330+
// Cached errors will be reported as soon as they are retrieved from the cache.
331+
_frontend.logger.debug(s"Verification has failed (errors: ${f.errors.map(_.readableMessage).mkString("\n")}; members: ${_ast.members.map(_.name).mkString(", ")})")
332+
_frontend.reporter report OverallFailureMessage(_frontend.getVerifierName, System.currentTimeMillis() - _frontend.startTime, f)
333+
}
334+
}
335+
248336
/**
249337
* Tries to determine which of the given errors are caused by the given method.
250338
* If an error's scope field is set, this information is used.
@@ -291,5 +379,10 @@ class ViperBackend(val backendName: String, private val _frontend: SilFrontend,
291379
Some(result.toList)
292380
}
293381

294-
def stop(): Unit = _frontend.verifier.stop()
295-
}
382+
private def turnEitherIntoVerificationResult(input: Either[Seq[AbstractError], VerificationResult]): VerificationResult = {
383+
input match {
384+
case Left(errs) => Failure(errs)
385+
case Right(res) => res
386+
}
387+
}
388+
}

0 commit comments

Comments
 (0)