Skip to content

Commit a8c8bce

Browse files
authored
Merge pull request #575 from tschoesi/final_merge_with_upstream
Report One Error Per Path, Recover From Pure Assertions
2 parents 4dbb81f + eb188d0 commit a8c8bce

18 files changed

Lines changed: 264 additions & 137 deletions

src/main/scala/Config.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,12 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") {
464464
noshort = true
465465
)
466466

467+
val numberOfErrorsToReport: ScallopOption[Int] = opt[Int]("numberOfErrorsToReport",
468+
descr = "Number of errors per member before the verifier stops. If this number is set to 0, all errors are reported.",
469+
default = Some(1),
470+
noshort = true
471+
)
472+
467473
val stateConsolidationMode: ScallopOption[StateConsolidationMode] = opt[StateConsolidationMode]("stateConsolidationMode",
468474
descr = s"One of the following modes:\n${StateConsolidationMode.helpText}",
469475
default = Some(StateConsolidationMode.Default),
@@ -502,6 +508,11 @@ class Config(args: Seq[String]) extends SilFrontendConfig(args, "Silicon") {
502508
default = Some(false),
503509
noshort = true
504510
)
511+
val enableBranchconditionReporting: ScallopOption[Boolean] = opt[Boolean]("enableBranchconditionReorting",
512+
descr = "Report branch conditions (can be useful for assertions that fail on multiple branches)",
513+
default = Some(false),
514+
noshort = true
515+
)
505516

506517
val setAxiomatizationFile: ScallopOption[String] = opt[String]("setAxiomatizationFile",
507518
descr =s"Source file with set axiomatisation. If omitted, built-in one is used.",

src/main/scala/Silicon.scala

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory
1717
import viper.silver.ast
1818
import viper.silver.frontend.{DefaultStates, SilFrontend}
1919
import viper.silver.reporter._
20-
import viper.silver.verifier.{DefaultDependency => SilDefaultDependency, Failure => SilFailure, Success => SilSuccess, TimeoutOccurred => SilTimeoutOccurred, VerificationResult => SilVerificationResult, Verifier => SilVerifier}
20+
import viper.silver.verifier.{Counterexample => SilCounterexample, DefaultDependency => SilDefaultDependency, Failure => SilFailure, Success => SilSuccess, TimeoutOccurred => SilTimeoutOccurred, VerificationResult => SilVerificationResult, Verifier => SilVerifier}
2121
import viper.silicon.common.config.Version
2222
import viper.silicon.interfaces.Failure
2323
import viper.silicon.logger.SymbExLogger
@@ -27,6 +27,8 @@ import viper.silver.cfg.silver.SilverCfg
2727
import viper.silver.logger.ViperStdOutLogger
2828
import viper.silver.plugin.PluginAwareReporter
2929

30+
import scala.util.chaining._
31+
3032
object Silicon {
3133
val name = BuildInfo.projectName
3234

@@ -247,12 +249,23 @@ class Silicon(val reporter: Reporter, private var debugInfo: Seq[(String, Any)]
247249
/*verifier.bookkeeper.*/elapsedMillis = System.currentTimeMillis() - /*verifier.bookkeeper.*/startTime
248250

249251
val failures =
250-
results.flatMap(r => r :: r.allPrevious)
251-
.collect{ case f: Failure => f } /* Ignore successes */
252-
.sortBy(_.message.pos match { /* Order failures according to source position */
253-
case pos: ast.HasLineColumn => (pos.line, pos.column)
254-
case _ => (-1, -1)
255-
})
252+
results.flatMap(r => r :: r.previous.toList)
253+
.collect{ case f: Failure => f } /* Ignore successes */
254+
.pipe(allResults => {
255+
/* If branchconditions are to be reported we collect the different failure contexts
256+
* of all failures that report the same error (but on different branches, with different CounterExample)
257+
* and put those into one failure */
258+
if (config.enableBranchconditionReporting())
259+
allResults.groupBy(_.message.readableMessage(withId = true, withPosition = true)).map{case (_: String, fs:List[Failure]) =>
260+
fs.head.message.failureContexts = fs.flatMap(_.message.failureContexts)
261+
Failure(fs.head.message)
262+
}.toList
263+
else allResults.distinctBy(f => f.message.readableMessage(withId = true, withPosition = true))
264+
})
265+
.sortBy(_.message.pos match { /* Order failures according to source position */
266+
case pos: ast.HasLineColumn => (pos.line, pos.column)
267+
case _ => (-1, -1)
268+
})
256269

257270
// if (config.showStatistics.isDefined) {
258271
// val proverStats = verifier.decider.statistics()
@@ -283,7 +296,7 @@ class Silicon(val reporter: Reporter, private var debugInfo: Seq[(String, Any)]
283296
failures
284297
}
285298

286-
private def logFailure(failure: Failure, log: String => Unit): Unit = {
299+
private def logFailure(failure: Failure, log: String => Unit): Unit = { //TODO:J log context?
287300
log("\n" + failure.message.readableMessage(withId = true, withPosition = true))
288301
}
289302

src/main/scala/decider/Decider.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ trait Decider {
3535

3636
def checkSmoke(): Boolean
3737

38-
def setCurrentBranchCondition(t: Term): Unit
38+
def setCurrentBranchCondition(t: Term, te: Option[ast.Exp] = None): Unit
3939
def setPathConditionMark(): Mark
4040

4141
def assume(t: Term): Unit
@@ -166,8 +166,8 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier =>
166166
//SymbExLogger.currentLog().closeScope(sepIdentifier)
167167
}
168168

169-
def setCurrentBranchCondition(t: Term): Unit = {
170-
pathConditions.setCurrentBranchCondition(t)
169+
def setCurrentBranchCondition(t: Term, te: Option[ast.Exp] = None): Unit = {
170+
pathConditions.setCurrentBranchCondition(t, te)
171171
assume(InsertionOrderedSet(Seq(t)))
172172
}
173173

src/main/scala/decider/PathConditions.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import viper.silicon.common.collections.immutable.InsertionOrderedSet
1010
import viper.silicon.Stack
1111
import viper.silicon.state.terms._
1212
import viper.silicon.utils.Counter
13-
13+
import viper.silver.ast
1414
/*
1515
* Interfaces
1616
*/
@@ -22,6 +22,7 @@ import viper.silicon.utils.Counter
2222

2323
trait RecordedPathConditions {
2424
def branchConditions: Stack[Term]
25+
def branchConditionExps: Stack[Option[ast.Exp]]
2526
def assumptions: InsertionOrderedSet[Term]
2627
def declarations: InsertionOrderedSet[Decl]
2728

@@ -39,7 +40,7 @@ trait RecordedPathConditions {
3940
}
4041

4142
trait PathConditionStack extends RecordedPathConditions {
42-
def setCurrentBranchCondition(condition: Term): Unit
43+
def setCurrentBranchCondition(condition: Term, conditionExp: Option[ast.Exp]): Unit
4344
def add(assumption: Term): Unit
4445
def add(declaration: Decl): Unit
4546
def pushScope(): Unit
@@ -59,11 +60,13 @@ private class PathConditionStackLayer
5960
extends Cloneable {
6061

6162
private var _branchCondition: Option[Term] = None
63+
private var _branchConditionExp: Option[Option[ast.Exp]] = None
6264
private var _globalAssumptions: InsertionOrderedSet[Term] = InsertionOrderedSet.empty
6365
private var _nonGlobalAssumptions: InsertionOrderedSet[Term] = InsertionOrderedSet.empty
6466
private var _declarations: InsertionOrderedSet[Decl] = InsertionOrderedSet.empty
6567

6668
def branchCondition: Option[Term] = _branchCondition
69+
def branchConditionExp: Option[Option[ast.Exp]] = _branchConditionExp
6770
def globalAssumptions: InsertionOrderedSet[Term] = _globalAssumptions
6871
def nonGlobalAssumptions: InsertionOrderedSet[Term] = _nonGlobalAssumptions
6972
def declarations: InsertionOrderedSet[Decl] = _declarations
@@ -79,6 +82,14 @@ private class PathConditionStackLayer
7982
_branchCondition = Some(condition)
8083
}
8184

85+
def branchConditionExp_=(condition: Option[ast.Exp]): Unit = {
86+
assert(_branchConditionExp.isEmpty,
87+
s"Branch condition is already set (to ${_branchConditionExp.get}), "
88+
+ s"won't override (with $condition).")
89+
90+
_branchConditionExp = Some(condition)
91+
}
92+
8293
def add(assumption: Term): Unit = {
8394
assert(
8495
!assumption.isInstanceOf[And],
@@ -116,6 +127,9 @@ private trait LayeredPathConditionStackLike {
116127
protected def branchConditions(layers: Stack[PathConditionStackLayer]): Stack[Term] =
117128
layers.flatMap(_.branchCondition)
118129

130+
protected def branchConditionExps(layers: Stack[PathConditionStackLayer]): Stack[Option[ast.Exp]] =
131+
layers.flatMap(_.branchConditionExp)
132+
119133
protected def assumptions(layers: Stack[PathConditionStackLayer]): InsertionOrderedSet[Term] =
120134
InsertionOrderedSet(layers.flatMap(_.assumptions)) // Note: Performance?
121135

@@ -175,6 +189,7 @@ private class DefaultRecordedPathConditions(from: Stack[PathConditionStackLayer]
175189
with RecordedPathConditions {
176190

177191
val branchConditions: Stack[Term] = branchConditions(from)
192+
val branchConditionExps: Stack[Option[ast.Exp]] = branchConditionExps(from)
178193
val assumptions: InsertionOrderedSet[Term] = assumptions(from)
179194
val declarations: InsertionOrderedSet[Decl] = declarations(from)
180195

@@ -209,10 +224,11 @@ private[decider] class LayeredPathConditionStack
209224

210225
pushScope() /* Create an initial layer on the stack */
211226

212-
def setCurrentBranchCondition(condition: Term): Unit = {
227+
def setCurrentBranchCondition(condition: Term, conditionExp: Option[ast.Exp]): Unit = {
213228
/* TODO: Split condition into top-level conjuncts as well? */
214229

215230
layers.head.branchCondition = condition
231+
layers.head.branchConditionExp = conditionExp
216232
}
217233

218234
def add(assumption: Term): Unit = {
@@ -275,6 +291,8 @@ private[decider] class LayeredPathConditionStack
275291

276292
def branchConditions: Stack[Term] = layers.flatMap(_.branchCondition)
277293

294+
override def branchConditionExps: Stack[Option[ast.Exp]] = layers.flatMap(_.branchConditionExp)
295+
278296
def assumptions: InsertionOrderedSet[Term] = allAssumptions
279297

280298
def declarations: InsertionOrderedSet[Decl] =

src/main/scala/interfaces/Verification.scala

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ package viper.silicon.interfaces
99
import viper.silicon.interfaces.state.Chunk
1010
import viper.silicon.reporting.Converter
1111
import viper.silicon.state.{State, Store}
12-
import viper.silver.verifier.{Counterexample, Model, VerificationError}
12+
import viper.silver.verifier.{Counterexample, FailureContext, Model, VerificationError}
1313
import viper.silicon.state.terms.Term
14+
import viper.silicon.verifier.Verifier
15+
import viper.silver.ast
1416

1517
/*
1618
* Results
@@ -22,25 +24,34 @@ import viper.silicon.state.terms.Term
2224

2325
/* TODO: Make VerificationResult immutable */
2426
sealed abstract class VerificationResult {
25-
var previous: Option[NonFatalResult] = None
27+
var previous: Vector[VerificationResult] = Vector() //Sets had problems with equality
28+
val continueVerification: Boolean = true
2629

2730
def isFatal: Boolean
2831
def &&(other: => VerificationResult): VerificationResult
2932

30-
def allPrevious: List[VerificationResult] =
31-
previous match {
32-
case None => Nil
33-
case Some(vr) => vr :: vr.allPrevious
34-
}
35-
36-
def append(other: NonFatalResult): VerificationResult =
37-
previous match {
38-
case None =>
39-
this.previous = Some(other)
40-
this
41-
case Some(vr) =>
42-
vr.append(other)
43-
}
33+
/* Attention: Parameter 'other' of 'combine' is a function! That is, the following
34+
* statements
35+
* println(other)
36+
* println(other)
37+
* will invoke the function twice, which might not be what you really want!
38+
*/
39+
def combine(other: => VerificationResult): VerificationResult = {
40+
if (this.continueVerification){
41+
val r: VerificationResult = other
42+
/* Result of combining a failure with a non failure should be a failure.
43+
* When combining two failures, the failure with 'continueVerification'
44+
* set to false (if any) should be the 'head' result */
45+
(this, r) match {
46+
case (_: FatalResult, _: FatalResult) | (_: FatalResult, _: NonFatalResult) if r.continueVerification =>
47+
this.previous = (this.previous :+ r) ++ r.previous
48+
this
49+
case _ =>
50+
r.previous = (r.previous :+ this) ++ this.previous
51+
r
52+
}
53+
} else this
54+
}
4455
}
4556

4657
sealed abstract class FatalResult extends VerificationResult {
@@ -60,7 +71,7 @@ sealed abstract class NonFatalResult extends VerificationResult {
6071
*/
6172
def &&(other: => VerificationResult): VerificationResult = {
6273
val r: VerificationResult = other
63-
r.append(this)
74+
r.previous = (r.previous :+ this) ++ this.previous
6475
r
6576
}
6677
}
@@ -76,8 +87,8 @@ case class Unreachable() extends NonFatalResult {
7687
case class Failure/*[ST <: Store[ST],
7788
H <: Heap[H],
7889
S <: State[ST, H, S]]*/
79-
(message: VerificationError)
80-
extends FatalResult {
90+
(message: VerificationError, override val continueVerification: Boolean = true)
91+
extends FatalResult {
8192

8293
/* TODO: Mutable state in a case class? DOOOOOOOOOOOOOON'T! */
8394
var load: Option[Seq[Term]] = None
@@ -89,6 +100,14 @@ case class Failure/*[ST <: Store[ST],
89100
override lazy val toString = message.readableMessage
90101
}
91102

103+
case class SilFailureContext(branchConditions: Seq[ast.Exp], counterExample: Option[Counterexample]) extends FailureContext {
104+
lazy val branchConditionString = if(branchConditions.nonEmpty)
105+
("\n\t\tunder branch conditions:\n" +
106+
branchConditions.map(bc => (bc.toString + " [ " + bc.pos.toString + " ] ")).mkString("\t\t"," ~~> ","") ) else ""
107+
lazy val counterExampleString = if(counterExample.isDefined) "\n\t\tcounterexample:\n" + counterExample.get.toString else ""
108+
override lazy val toString = branchConditionString + counterExampleString
109+
}
110+
92111
trait SiliconCounterexample extends Counterexample {
93112
val internalStore: Store
94113
lazy val store: Map[String, Term] = internalStore.values.map{case (k, v) => k.name -> v}

src/main/scala/reporting/package.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ package object reporting {
4646
def convertToViperResult(result: VerificationResult): VprVerificationResult = {
4747
result match {
4848
case Success() | Unreachable() => VprSuccess
49-
case Failure(message) => VprFailure(Seq(message))
49+
case Failure(message, _) => VprFailure(Seq(message))
5050
}
5151
}
5252

src/main/scala/rpi/inference/teacher/SampleExtractor.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,11 @@ trait SampleExtractor extends AbstractTeacher with LazyLogging {
240240
*/
241241
private def extractInformation[T](error: VerificationError): (Counter, ast.LocationAccess, Option[T]) = {
242242
// extract counter example
243-
val counter = error.counterexample match {
244-
case Some(value: Counter) => value
245-
case _ => sys.error("Verification error does not contain a counter example.")
246-
}
243+
val counters = error.failureContexts.map(_.counterExample).map {case Some(counterexample: SiliconRawCounterexample) => counterexample}
244+
// We should only have multiple CEs at the very end of verification when results are merged
245+
if (counters.length != 1)
246+
sys.error(s"Verification error does not contain exactly one counterexample")
247+
val counter = counters.head
247248
// extract offending location
248249
val offending = error.reason match {
249250
case InsufficientPermission(location) => location

src/main/scala/rules/Brancher.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import viper.silicon.logger.SymbExLogger
1313
import viper.silicon.state.State
1414
import viper.silicon.state.terms.{Not, Term}
1515
import viper.silicon.verifier.Verifier
16+
import viper.silver.ast
1617

1718
trait BranchingRules extends SymbolicExecutionRules {
1819
def branch(s: State,
1920
condition: Term,
21+
conditionExp: Option[ast.Exp],
2022
v: Verifier,
2123
fromShortCircuitingAnd: Boolean = false)
2224
(fTrue: (State, Verifier) => VerificationResult,
@@ -27,13 +29,15 @@ trait BranchingRules extends SymbolicExecutionRules {
2729
object brancher extends BranchingRules {
2830
def branch(s: State,
2931
condition: Term,
32+
conditionExp: Option[ast.Exp],
3033
v: Verifier,
3134
fromShortCircuitingAnd: Boolean = false)
3235
(fThen: (State, Verifier) => VerificationResult,
3336
fElse: (State, Verifier) => VerificationResult)
3437
: VerificationResult = {
3538

3639
val negatedCondition = Not(condition)
40+
val negatedConditionExp = conditionExp.fold[Option[ast.Exp]](None)(c => Some(ast.Not(c)(pos = conditionExp.get.pos, info = conditionExp.get.info, ast.NoTrafos)))
3741
val parallelizeElseBranch = s.parallelizeBranches && !s.underJoin
3842

3943
/* Skip path feasibility check if one of the following holds:
@@ -118,7 +122,7 @@ object brancher extends BranchingRules {
118122
}
119123

120124
v1.decider.prover.comment(s"[else-branch: $cnt | $negatedCondition]")
121-
v1.decider.setCurrentBranchCondition(negatedCondition)
125+
v1.decider.setCurrentBranchCondition(negatedCondition, negatedConditionExp)
122126

123127
fElse(v1.stateConsolidator.consolidateIfRetrying(s1, v1), v1)
124128
})
@@ -151,13 +155,13 @@ object brancher extends BranchingRules {
151155
SymbExLogger.currentLog().markReachable(uidBranchPoint)
152156
executionFlowController.locally(s, v)((s1, v1) => {
153157
v1.decider.prover.comment(s"[then-branch: $cnt | $condition]")
154-
v1.decider.setCurrentBranchCondition(condition)
158+
v1.decider.setCurrentBranchCondition(condition, conditionExp)
155159

156160
fThen(v1.stateConsolidator.consolidateIfRetrying(s1, v1), v1)
157161
})
158162
} else {
159163
Unreachable()
160-
}) && {
164+
}) combine {
161165

162166
/* [BRANCH-PARALLELISATION] */
163167
if (parallelizeElseBranch) {

0 commit comments

Comments
 (0)