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
77package viper .server .core
88
@@ -11,7 +11,7 @@ import viper.carbon.CarbonFrontend
1111import viper .server .vsi .Envelope
1212import viper .silicon .{Silicon , SiliconFrontend }
1313import viper .silver .ast ._
14- import viper .silver .frontend .{ DefaultStates , SilFrontend }
14+ import viper .silver .frontend .SilFrontend
1515import viper .silver .reporter .{Reporter , _ }
1616import 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+ */
108113class 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