Skip to content

Commit d7fa226

Browse files
authored
Merge pull request #641 from viperproject/plugin-entity-result-translation
[Plugin] Translation of Entity Verification Results
2 parents fd5c009 + 98d8c81 commit d7fa226

10 files changed

Lines changed: 172 additions & 109 deletions

File tree

src/main/scala/viper/silver/frontend/SilFrontend.scala

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,41 @@ trait SilFrontend extends DefaultFrontend {
215215
}
216216
}
217217

218-
override def verification() = {
218+
override def verification(): Unit = {
219+
def filter(input: Program): Result[Program] = {
220+
_plugins.beforeMethodFilter(input) match {
221+
case Some(inputPlugin) =>
222+
// Filter methods according to command-line arguments.
223+
val verifyMethods =
224+
if (config != null && config.methods() != ":all") Seq("methods", config.methods())
225+
else inputPlugin.methods map (_.name)
226+
227+
val methods = inputPlugin.methods filter (m => verifyMethods.contains(m.name))
228+
val program = Program(inputPlugin.domains, inputPlugin.fields, inputPlugin.functions, inputPlugin.predicates, methods, inputPlugin.extensions)(inputPlugin.pos, inputPlugin.info, inputPlugin.errT)
229+
230+
_plugins.beforeVerify(program) match {
231+
case Some(programPlugin) => Succ(programPlugin)
232+
case None => Fail(_plugins.errors)
233+
}
234+
235+
case None => Fail(_plugins.errors)
236+
}
237+
}
238+
239+
if (state == DefaultStates.ConsistencyCheck && _errors.isEmpty) {
240+
filter(_program.get) match {
241+
case Succ(program) => _program = Some(program)
242+
case Fail(errors) => _errors ++= errors
243+
}
244+
}
219245
super.verification()
220-
_verificationResult = _verificationResult.map(_plugins.mapVerificationResult)
246+
_verificationResult = _verificationResult.map(_plugins.mapVerificationResult(_program.get, _))
221247
}
222248

223249
def finish(): Unit = {
224-
_plugins.beforeFinish(result) match {
250+
val res = _plugins.beforeFinish(result)
251+
_verificationResult = Some(res)
252+
res match {
225253
case Success =>
226254
reporter report OverallSuccessMessage(verifier.name, getTime)
227255
case f: Failure =>
@@ -301,32 +329,12 @@ trait SilFrontend extends DefaultFrontend {
301329
}
302330

303331
def doConsistencyCheck(input: Program): Result[Program]= {
304-
def filter(input: Program): Result[Program] = {
305-
_plugins.beforeMethodFilter(input) match {
306-
case Some(inputPlugin) =>
307-
// Filter methods according to command-line arguments.
308-
val verifyMethods =
309-
if (config != null && config.methods() != ":all") Seq("methods", config.methods())
310-
else inputPlugin.methods map (_.name)
311-
312-
val methods = inputPlugin.methods filter (m => verifyMethods.contains(m.name))
313-
val program = Program(inputPlugin.domains, inputPlugin.fields, inputPlugin.functions, inputPlugin.predicates, methods, inputPlugin.extensions)(inputPlugin.pos, inputPlugin.info)
314-
315-
_plugins.beforeVerify(program) match {
316-
case Some(programPlugin) => Succ(programPlugin)
317-
case None => Fail(_plugins.errors)
318-
}
319-
320-
case None => Fail(_plugins.errors)
321-
}
322-
}
323-
324332
var errors = input.checkTransitively
325333
if (backendTypeFormat.isDefined)
326334
errors = errors ++ Consistency.checkBackendTypes(input, backendTypeFormat.get)
327-
if (errors.isEmpty)
328-
filter(input)
329-
else
335+
if (errors.isEmpty) {
336+
Succ(input)
337+
} else
330338
Fail(errors)
331339
}
332340
}

src/main/scala/viper/silver/plugin/PluginTemplate.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package viper.silver.plugin
88

99
import viper.silver.ast.Program
1010
import viper.silver.parser.PProgram
11+
import viper.silver.reporter.Entity
1112
import viper.silver.verifier.{AbstractError, VerificationResult}
1213

1314
// The aim of this file is just to give a skeleton implementation to copy for a particular plugin implementation.
@@ -53,13 +54,24 @@ class PluginTemplate extends SilverPlugin {
5354
*/
5455
override def beforeVerify(input: Program) : Program = ???
5556

57+
/** Called after the verification of an entity, which is used to stream verification results to the IDE
58+
* (which happens as soon as a member has been verified). Error transformation should happen here.
59+
* This will only be called if verification of `entity` took place.
60+
*
61+
* @param entity Entity to which `input` belongs
62+
* @param input Result of verification
63+
* @return Modified result
64+
*/
65+
override def mapEntityVerificationResult(entity: Entity, input: VerificationResult): VerificationResult = ???
66+
5667
/** Called after the verification. Error transformation should happen here.
5768
* This will only be called if verification took place.
5869
*
70+
* @param program Viper AST
5971
* @param input Result of verification
6072
* @return Modified result
6173
*/
62-
override def mapVerificationResult(input: VerificationResult): VerificationResult = ???
74+
override def mapVerificationResult(program: Program, input: VerificationResult): VerificationResult = ???
6375

6476
/** Called after the verification just before the result is printed. Will not be called in tests.
6577
* This will also be called even if verification did not take place (i.e. an error during parsing/translation occurred).

src/main/scala/viper/silver/plugin/SilverPlugin.scala

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ch.qos.logback.classic.Logger
1010
import viper.silver.ast.Program
1111
import viper.silver.frontend.SilFrontendConfig
1212
import viper.silver.parser.{FastParser, PProgram}
13-
import viper.silver.reporter.Reporter
13+
import viper.silver.reporter.{Entity, Reporter}
1414
import viper.silver.verifier.{AbstractError, VerificationResult}
1515

1616
import scala.annotation.unused
@@ -74,13 +74,24 @@ trait SilverPlugin {
7474
*/
7575
def beforeVerify(input: Program) : Program = input
7676

77+
/** Called after the verification of an entity, which is used to stream verification results to the IDE
78+
* (which happens as soon as a member has been verified). Error transformation should happen here.
79+
* This will only be called if verification of `entity` took place.
80+
*
81+
* @param entity Entity to which `input` belongs
82+
* @param input Result of verification
83+
* @return Modified result
84+
*/
85+
def mapEntityVerificationResult(entity: Entity, input: VerificationResult): VerificationResult = input
86+
7787
/** Called after the verification. Error transformation should happen here.
7888
* This will only be called if verification took place.
7989
*
90+
* @param program Viper AST
8091
* @param input Result of verification
8192
* @return Modified result
8293
*/
83-
def mapVerificationResult(input: VerificationResult): VerificationResult = input
94+
def mapVerificationResult(program: Program, input: VerificationResult): VerificationResult = input
8495

8596
/** Called after the verification just before the result is printed. Will not be called in tests.
8697
* This will also be called even if verification did not take place (i.e. an error during parsing/translation occurred).

src/main/scala/viper/silver/plugin/SilverPluginManager.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ch.qos.logback.classic.Logger
1010
import viper.silver.ast._
1111
import viper.silver.frontend.SilFrontendConfig
1212
import viper.silver.parser.{FastParser, PProgram}
13-
import viper.silver.reporter.Reporter
13+
import viper.silver.reporter.{Entity, Reporter}
1414
import viper.silver.verifier.{AbstractError, VerificationResult}
1515

1616
/** Manage the loaded plugins and execute them during the different hooks (see [[viper.silver.plugin.SilverPlugin]]).
@@ -54,8 +54,11 @@ class SilverPluginManager(val plugins: Seq[SilverPlugin]) {
5454
def beforeVerify(input: Program): Option[Program] =
5555
foldWithError(input)((inp, plugin) => plugin.beforeVerify(inp))
5656

57-
def mapVerificationResult(input: VerificationResult): VerificationResult =
58-
plugins.foldLeft(input)((inp, plugin) => plugin.mapVerificationResult(inp))
57+
def mapEntityVerificationResult(entity: Entity, input: VerificationResult): VerificationResult =
58+
plugins.foldLeft(input)((inp, plugin) => plugin.mapEntityVerificationResult(entity, inp))
59+
60+
def mapVerificationResult(program: Program, input: VerificationResult): VerificationResult =
61+
plugins.foldLeft(input)((inp, plugin) => plugin.mapVerificationResult(program, inp))
5962

6063
def beforeFinish(input: VerificationResult): VerificationResult =
6164
plugins.foldLeft(input)((inp, plugin) => plugin.beforeFinish(inp))

src/main/scala/viper/silver/plugin/standard/predicateinstance/PredicateInstancePlugin.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import viper.silver.verifier.{ConsistencyError, Failure, Success, VerificationRe
1515
import viper.silver.verifier.errors.PreconditionInAppFalse
1616
import fastparse._
1717
import viper.silver.parser.FastParserCompanion.whitespace
18+
import viper.silver.reporter.Entity
1819

1920
import scala.annotation.unused
2021
import scala.collection.immutable.ListMap
@@ -95,10 +96,16 @@ class PredicateInstancePlugin(@unused reporter: viper.silver.reporter.Reporter,
9596
newProgram
9697
}
9798

99+
override def mapEntityVerificationResult(entity: Entity, input: VerificationResult): VerificationResult =
100+
translateVerificationResult(input)
101+
98102
/**
99103
* Initiate the error transformer for possibly predicate instances related errors
100104
*/
101-
override def mapVerificationResult(input: VerificationResult): VerificationResult = {
105+
override def mapVerificationResult(@unused program: Program, input: VerificationResult): VerificationResult =
106+
translateVerificationResult(input)
107+
108+
private def translateVerificationResult(input: VerificationResult): VerificationResult = {
102109
input match {
103110
case Success => input
104111
case Failure(errors) =>

src/main/scala/viper/silver/plugin/standard/refute/RefuteASTExtension.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import viper.silver.ast.pretty.FastPrettyPrinter.{ContOps, text, toParenDoc}
1111
import viper.silver.ast.pretty.PrettyPrintPrimitives
1212

1313
/** An `FailureExpectedInfo` info that tells us that this assert is a refute. */
14-
case object RefuteInfo extends FailureExpectedInfo
14+
case class RefuteInfo(refute: Refute) extends FailureExpectedInfo
1515

1616
case class Refute(exp: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends ExtensionStmt {
1717
override lazy val prettyPrint: PrettyPrintPrimitives#Cont = text("refute") <+> toParenDoc(exp)

src/main/scala/viper/silver/plugin/standard/refute/RefutePlugin.scala

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import viper.silver.ast._
1212
import viper.silver.parser.FastParserCompanion.whitespace
1313
import viper.silver.parser.FastParser
1414
import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin}
15+
import viper.silver.reporter.Entity
1516
import viper.silver.verifier._
1617
import viper.silver.verifier.errors.AssertFailed
1718

@@ -27,8 +28,6 @@ class RefutePlugin(@unused reporter: viper.silver.reporter.Reporter,
2728
/** Keyword used to define refute statements. */
2829
private val refuteKeyword: String = "refute"
2930

30-
private var refuteAsserts: Map[Position, Refute] = Map()
31-
3231
/** Parser for refute statements. */
3332
def refute[_: P]: P[PRefute] =
3433
FP(keyword(refuteKeyword) ~/ exp).map{ case (pos, e) => PRefute(e)(pos) }
@@ -46,40 +45,62 @@ class RefutePlugin(@unused reporter: viper.silver.reporter.Reporter,
4645
* Remove refute statements from the AST and add them as non-det asserts.
4746
* The ⭐ is nice since such a variable name cannot be parsed, but will it cause issues?
4847
*/
49-
override def beforeVerify(input: Program): Program =
50-
ViperStrategy.Slim({
51-
case r@Refute(exp) => {
52-
this.refuteAsserts += (r.pos -> r)
53-
Seqn(Seq(
54-
If(LocalVar(s"__plugin_refute_nondet${this.refuteAsserts.size}", Bool)(r.pos),
55-
Seqn(Seq(
56-
Assert(exp)(r.pos, RefuteInfo),
57-
Inhale(BoolLit(false)(r.pos))(r.pos)
58-
), Seq())(r.pos),
59-
Seqn(Seq(), Seq())(r.pos))(r.pos)
48+
override def beforeVerify(input: Program): Program = {
49+
val transformedMethods = input.methods.map(method => {
50+
var refutesInMethod = 0
51+
ViperStrategy.Slim({
52+
case r@Refute(exp) =>
53+
refutesInMethod += 1
54+
val nonDetLocalVarDecl = LocalVarDecl(s"__plugin_refute_nondet$refutesInMethod", Bool)(r.pos)
55+
Seqn(Seq(
56+
If(nonDetLocalVarDecl.localVar,
57+
Seqn(Seq(
58+
Assert(exp)(r.pos, RefuteInfo(r)),
59+
Inhale(BoolLit(false)(r.pos))(r.pos)
60+
), Seq())(r.pos),
61+
Seqn(Seq(), Seq())(r.pos))(r.pos)
6062
),
61-
Seq(LocalVarDecl(s"__plugin_refute_nondet${this.refuteAsserts.size}", Bool)(r.pos))
62-
)(r.pos)
63-
}
64-
}).recurseFunc({
65-
case Method(_, _, _, _, _, body) => Seq(body)
66-
}).execute(input)
63+
Seq(nonDetLocalVarDecl)
64+
)(r.pos)
65+
}).recurseFunc({
66+
case Method(_, _, _, _, _, body) => Seq(body)
67+
}).execute[Method](method)
68+
})
69+
Program(input.domains, input.fields, input.functions, input.predicates, transformedMethods, input.extensions)(input.pos, input.info, input.errT)
70+
}
71+
72+
/** Remove refutation related errors for the current entity and add refuteAsserts in this entity that didn't report an error. */
73+
override def mapEntityVerificationResult(entity: Entity, input: VerificationResult): VerificationResult =
74+
mapVerificationResultsForNode(entity, input)
6775

68-
/** Remove refutation related errors and add refuteAsserts that didn't report an error. */
69-
override def mapVerificationResult(input: VerificationResult): VerificationResult = {
70-
val errors: Seq[AbstractError] = (input match {
71-
case Success => Seq()
72-
case Failure(errors) => {
73-
errors.filter {
74-
case AssertFailed(a, _, _) if a.info == RefuteInfo => {
75-
this.refuteAsserts -= a.pos
76-
false
77-
}
78-
case _ => true
79-
}
76+
/** Remove refutation related errors (for all entities) and add refuteAsserts (for all entities) that didn't report an error. */
77+
override def mapVerificationResult(program: Program, input: VerificationResult): VerificationResult =
78+
mapVerificationResultsForNode(program, input)
79+
80+
private def mapVerificationResultsForNode(n: Node, input: VerificationResult): VerificationResult = {
81+
val (refutesForWhichErrorOccurred, otherErrors) = input match {
82+
case Success => (Seq.empty, Seq.empty)
83+
case Failure(errors) => errors.partitionMap {
84+
case AssertFailed(NodeWithInfo(RefuteInfo(r)), _, _) => Left((r, r.pos))
85+
case err => Right(err)
8086
}
81-
}) ++ this.refuteAsserts.map(r => RefuteFailed(r._2, RefutationTrue(r._2.exp)))
82-
if (errors.length == 0) Success
87+
}
88+
val refutesContainedInNode = n.collect {
89+
case NodeWithInfo(RefuteInfo(r)) => (r, r.pos)
90+
}
91+
// note that we include positional information in `refutesForWhichErrorOccurred` and `refutesContainedInNode` such
92+
// that we do not miss errors just because the same refute statement occurs multiple times
93+
val refutesForWhichNoErrorOccurred = refutesContainedInNode.filterNot(refutesForWhichErrorOccurred.contains(_))
94+
val missingErrorsInNode = refutesForWhichNoErrorOccurred.map{
95+
case (refute, _) => RefuteFailed(refute, RefutationTrue(refute.exp))
96+
}
97+
98+
val errors = otherErrors ++ missingErrorsInNode
99+
if (errors.isEmpty) Success
83100
else Failure(errors)
84101
}
85102
}
103+
104+
object NodeWithInfo {
105+
def unapply(node : Node) : Option[Info] = Some(node.meta._2)
106+
}

src/main/scala/viper/silver/plugin/standard/termination/TerminationPlugin.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import viper.silver.parser._
1313
import viper.silver.plugin.standard.predicateinstance.PPredicateInstance
1414
import viper.silver.plugin.standard.termination.transformation.Trafo
1515
import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin}
16-
import viper.silver.verifier.errors.AssertFailed
16+
import viper.silver.verifier.errors.{AssertFailed, PreconditionInAppFalse}
1717
import viper.silver.verifier._
1818
import fastparse._
1919
import viper.silver.parser.FastParserCompanion.whitespace
20+
import viper.silver.reporter.Entity
2021

2122
import scala.annotation.unused
2223

@@ -121,10 +122,16 @@ class TerminationPlugin(@unused reporter: viper.silver.reporter.Reporter,
121122
}
122123
}
123124

125+
override def mapEntityVerificationResult(entity: Entity, input: VerificationResult): VerificationResult =
126+
translateVerificationResult(input)
127+
124128
/**
125-
* Call the error transformation on possibly termination related errors.
126-
*/
127-
override def mapVerificationResult(input: VerificationResult): VerificationResult = {
129+
* Call the error transformation on possibly termination related errors.
130+
*/
131+
override def mapVerificationResult(@unused program: Program, input: VerificationResult): VerificationResult =
132+
translateVerificationResult(input)
133+
134+
private def translateVerificationResult(input: VerificationResult): VerificationResult = {
128135
if (deactivated) return input // if decreases checks are deactivated no verification result mapping is required.
129136

130137
input match {

0 commit comments

Comments
 (0)