Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display Failing Branches #891

Draft
wants to merge 39 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8c16f3d
Display Failing Branches
7i6ht Jan 18, 2025
1d7424d
Store trees & move class
7i6ht Jan 30, 2025
4f7a805
Clean up
7i6ht Jan 31, 2025
ce1c035
Refactoring + fixes
7i6ht Feb 2, 2025
a441829
Refactoring
7i6ht Feb 4, 2025
472434b
Refactoring
7i6ht Feb 5, 2025
7b25a2e
Add tests
7i6ht Feb 5, 2025
eb50f57
Fix tests
7i6ht Feb 5, 2025
ff10687
Branch Tree: Print single path
7i6ht Feb 17, 2025
213df1d
Fix tests
7i6ht Feb 17, 2025
d8856bc
Really fix tests
7i6ht Feb 17, 2025
1251bb1
Fix tests
7i6ht Feb 19, 2025
d093d43
Revert changes to brancher
7i6ht Feb 26, 2025
b82921f
Revert unwanted changes to consumer
7i6ht Feb 26, 2025
e1c0494
Revert unwanted changes to state
7i6ht Feb 26, 2025
2c05828
Revert unwanted changes to state
7i6ht Feb 26, 2025
23682f9
Revert unwanted changes to state
7i6ht Feb 26, 2025
7a468de
Revert unwanted changes to DefaultMainVerifier
7i6ht Feb 26, 2025
28a6cc0
Revert unwanted changes to State
7i6ht Feb 26, 2025
7d3b6f1
Collect all failures
7i6ht Feb 28, 2025
f41d858
Change size check
7i6ht Mar 2, 2025
326c5e5
Remove braces
7i6ht Mar 2, 2025
bfe81d3
Fix
7i6ht Mar 2, 2025
c461458
Renaming
7i6ht Mar 2, 2025
63898f1
Clean up
7i6ht Mar 3, 2025
01f7bf1
Dot representation + report whole tree
7i6ht Mar 4, 2025
93189b9
Concurrent Map inside branchTreeMap
7i6ht Mar 4, 2025
4eb19d9
Make dot file path absolute
7i6ht Mar 4, 2025
089fc05
Add missing change
7i6ht Mar 7, 2025
29d114d
Add missing change
7i6ht Mar 7, 2025
370c675
Change back from reporting to errors
7i6ht Mar 7, 2025
bf1a48d
Refactoring
7i6ht Mar 9, 2025
692e9e2
Refactoring
7i6ht Mar 9, 2025
9a70060
Refactoring
7i6ht Mar 9, 2025
1a04d4f
Refactoring
7i6ht Mar 9, 2025
11f2d7a
Undo changes
7i6ht Mar 10, 2025
3f11384
Remove TODO
7i6ht Mar 10, 2025
d35072c
Readd missing files
7i6ht Mar 10, 2025
2a563d1
Move BranchTree
7i6ht Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 297 additions & 0 deletions src/main/scala/branchTree/BranchTree.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package viper.silicon.branchTree

import viper.silicon.common.io.PrintWriter
import viper.silicon.verifier.Verifier
import viper.silver.ast
import viper.silver.ast.Exp

import scala.annotation.tailrec

class BranchTree extends viper.silver.verifier.errors.BranchTree {

private def incrementIfFatal(currBranchResultFatal: Int, isResultFatal: Boolean) : Int =
if (isResultFatal) Math.max(currBranchResultFatal,0)+1 else currBranchResultFatal

protected[branchTree] def extend(branchConditions : Seq[Exp], isResultFatal: Boolean) : Unit = {
(this, branchConditions) match {
case (b:Branch, bcs) if bcs.nonEmpty =>
var currBranch = b
var negated = branchConditions.head match {
case _: ast.Not => true
case _ => false
}
var tail = branchConditions.tail
var next = true
while (tail.nonEmpty && next) {
next = false
val headExp = tail.head match {
case ast.Not(exp) => exp
case _ => tail.head
}
(currBranch.left, currBranch.right) match {
case (lb@Branch(exp,_,_,_,_),_) if headExp.toString == exp.toString && negated =>
currBranch.leftResFatalCount = incrementIfFatal(currBranch.leftResFatalCount,isResultFatal)
currBranch = lb
next = true
case (_,rb@Branch(exp,_,_,_,_)) if headExp.toString == exp.toString && !negated =>
currBranch.rightResFatalCount = incrementIfFatal(currBranch.rightResFatalCount,isResultFatal)
currBranch = rb
next = true
case _ =>
}
if (next) {
negated = tail.head match {
case _: ast.Not => true
case _ => false
}
tail = tail.tail
}
}
val errorCount = if (isResultFatal) 1 else -1
val subTree = BranchTree.generate(Vector((tail, isResultFatal)))// -1 for successful result
if (negated) {
currBranch.left = subTree.get
currBranch.leftResFatalCount = errorCount
} else {
currBranch.right = subTree.get
currBranch.rightResFatalCount = errorCount
}
case _=>
}
}


private def even(n: Int) = (n & 1) == 0
private def buildTreeStrRec(fatalCount: Int) : (Vector[String], Int, Int) = {
this match {
case Leaf if fatalCount == -1 => (Vector("✔"), 0, 0)
case Leaf if fatalCount > 0 => (Vector("Error"), 2, 2) // ✘
case _ : Branch => this.buildTreeStr()
case _ => (Vector("?"), 0, 0)
}
}
private def buildTreeStr() : (Vector[String], Int, Int) = {
this match {
case Branch(exp, left, right, leftErrCount, rightErrCount) =>
val expStr = exp.toString
val expStrLen = expStr.length

var boxMiddle = "│ " + expStr + (if (even(expStr.length)) " " else "") + " │"
val boxLen = boxMiddle.length
val halfBoxLen = boxLen / 2

var (leftStrVec, _, prevLeftRightBoxLen) = left.buildTreeStrRec(leftErrCount)
var (rightStrVec, prevRightLeftBoxLen, _) = right.buildTreeStrRec(rightErrCount)

val halfExpStrLen = expStrLen / 2
val leftBoxLen = leftStrVec.head.length
val rightBoxLen = rightStrVec.head.length

var leftFiller = halfBoxLen - leftBoxLen
if (leftFiller > 0) {
leftStrVec = leftStrVec.map(str => " " * leftFiller + str)
leftFiller = 0
} else {
leftFiller = -leftFiller
}

var rightFiller = halfBoxLen - rightBoxLen
if (rightFiller > 0) {
rightStrVec = rightStrVec.map(str => str + " " * rightFiller)
rightFiller = 0
} else {
rightFiller = -rightFiller
}

val boxTop = " " * leftFiller + "┌─" + ("─" * halfExpStrLen) + "┴" + ("─" * halfExpStrLen) + "─┐" + " " * rightFiller
boxMiddle = " " * leftFiller + boxMiddle + " " * rightFiller
val boxBottom = " " * leftFiller + "└─" + "─" * halfExpStrLen + "┬" + "─" * halfExpStrLen + "─┘" + " " * rightFiller

leftStrVec = leftStrVec.map(str => str + " ")
leftStrVec +:= " " * (leftFiller+halfExpStrLen-prevLeftRightBoxLen) + "F┌" + "─" * prevLeftRightBoxLen + "┴"
rightStrVec +:= "─" * prevRightLeftBoxLen + "┐T" + " " * (rightFiller+halfExpStrLen-prevRightLeftBoxLen)

if (leftStrVec.length > rightStrVec.length){
for(_ <- 0 to leftStrVec.length - rightStrVec.length)
{
rightStrVec :+= " "*rightStrVec.head.length
}
} else {
for(_ <- 0 to rightStrVec.length - leftStrVec.length)
{
leftStrVec :+= " "*leftStrVec.head.length
}
}
(boxTop +: boxMiddle +: boxBottom +: (leftStrVec zip rightStrVec).map(t => t._1 + t._2), leftFiller + halfBoxLen, rightFiller + halfBoxLen)
case _ => (Vector.empty, -1, -1) // Should not happen
}
}


private def fill(vec : Vector[String], filler :Int): Vector[String] = {
vec.grouped(4)
.flatMap(elems => {
Vector(
" "*filler + elems(0) + " "*filler,
" "*filler + elems(1) + "─"*filler,
" "*filler + elems(2) + " "*filler,
" "*filler + elems(3) + " "*filler
)
}).toVector
}
private def buildPathStr() : String = {
var currTree : BranchTree = this
var maxBoxLen = 5 // for 'Error'
var path = Vector[String]()
var side = Vector[String]()
while (currTree != Leaf) {
currTree match {
case b@Branch(e, l, r, lc, rc) =>
val expStr = e.toString
val halfExpStrLen = expStr.length / 2
val (pathTaken, pathNotTaken) = if (b.isRightFatal) ("T", "F") else ("F","T")

val boxTop = "┌─" + ("─" * halfExpStrLen) + "┴" + ("─" * halfExpStrLen) + s"─┐ $pathNotTaken "
val boxMiddle = "│ " + expStr + (if (even(expStr.length)) " " else "") + " ├──"
val boxBottom = "└─" + "─" * halfExpStrLen + "┬" + "─" * halfExpStrLen + "─┘ "
val conDown = " " * (halfExpStrLen+2) + s"│$pathTaken " + " " * halfExpStrLen
var box = Vector(boxTop, boxMiddle, boxBottom, conDown)

val boxLen = boxMiddle.length-2
val filler = Math.abs(maxBoxLen - boxLen) / 2
if (maxBoxLen > boxLen) box = fill(box, filler) else path = fill(path, filler)
maxBoxLen = Math.max(maxBoxLen, boxLen)

(if(b.isRightFatal) lc else rc) match {
case -1 => side ++= Vector("\n"," ✔\n","\n","\n")
case 0 => side ++= Vector("\n"," ?\n","\n","\n")
case _ => side ++= Vector("\n"," Error\n","\n","\n")
}

path ++= box
currTree = if (b.isRightFatal) r else l // influenced by order of verification results (true branch results before left)
case _ => currTree = Leaf
}
}
val filler = maxBoxLen/2
val combined = path.zip(side).map(t => t._1+t._2)
((" "*filler + "│" + " "*filler + "\n") +: combined :+ (" "*(filler-2)+"Error\n"))
.reduce((str, s) => str + s)
}

def getErrorCount: Int = {
this match {
case Branch(_,_,_,lf,rf) => Math.max(lf,0) + Math.max(rf,0)
case _ => 0
}
}

def prettyPrint() : String = {
if (Verifier.config.numberOfErrorsToReport() == 1 || this.getErrorCount == 1) {
this.buildPathStr()
} else {
this.buildTreeStr()._1.reduce((str, s) => str + "\n" + s) + "\n"
}
}

private def leafToDotNodeContent(fatalCount : Int): String = {
fatalCount match {
case -1 => "label=\"✔\",shape=\"octagon\",style=\"filled\", fillcolor=\"palegreen\""
case 1 => "label=\"Error\",shape=\"octagon\",style=\"filled\", fillcolor=\"lightsalmon\""
case _ => "label=\"?\",shape=\"octagon\",style=\"filled\", fillcolor=\"lightgoldenrodyellow\""
}
}
private def writeDotFileRec(writer: java.io.PrintWriter, visitedCount : Int = 0) : Int = {
this match {
case Branch(exp,left,right,leftResFatalCount,rightResFatalCount) =>
val parentIdn = s"B$visitedCount"
writer.write(s" $parentIdn[shape=\"square\",label=\"${exp.toString}\"];\n")
val newVisitedCountLeft = visitedCount + 1
val visitedCountLeft = left match {
case _ : Branch =>
val leftBranchIdn = s"B$newVisitedCountLeft"
val visitedCountLeft_ = left.writeDotFileRec(writer, newVisitedCountLeft)
writer.write(s" $parentIdn -> $leftBranchIdn[label=\"F\"];\n")
visitedCountLeft_
case Leaf =>
val leftLeafIdn = s"B$newVisitedCountLeft"
writer.write(s" $leftLeafIdn[${leafToDotNodeContent(leftResFatalCount)}];\n")
writer.write(s" $parentIdn -> $leftLeafIdn [label=\"F\"];\n")
newVisitedCountLeft
}
val newVisitedCountRight = visitedCountLeft + 1
val visitedCountRight = right match {
case _ : Branch =>
val rightBranchIdn = s"B$newVisitedCountRight"
val visitedCountRight_ = right.writeDotFileRec(writer, newVisitedCountRight)
writer.write(s" $parentIdn -> $rightBranchIdn[label=\"T\"];\n")
visitedCountRight_
case Leaf =>
val rightLeafIdn = s"B$newVisitedCountRight"
writer.write(s" $rightLeafIdn[${leafToDotNodeContent(rightResFatalCount)}];\n")
writer.write(s" $parentIdn -> $rightLeafIdn [label=\"T\"];\n")
newVisitedCountRight
}
visitedCountRight
case _ => 0
}
}

def toDotFile(): Unit = {
val writer = PrintWriter(new java.io.File(BranchTree.DotFilePath),append=true)
writer.write("digraph {\n")
this.writeDotFileRec(writer)
writer.write("}\n")
writer.close()
}
}

object BranchTree {
val DotFilePath = s"${System.getProperty("user.dir")}/${Verifier.config.tempDirectory()}/BranchTree.dot"

@tailrec
private def generatePathRec(expressions: Seq[Exp], errorCount: Int, result: BranchTree): BranchTree = {
expressions.length match {
case 0 => result
case _ =>
val lastExp = expressions.last
lastExp match {
case ast.Not(exp) =>
generatePathRec(expressions.init, errorCount, Branch(exp, result, Leaf, errorCount, 0))
case _ =>
generatePathRec(expressions.init, errorCount, Branch(lastExp, Leaf, result, 0, errorCount))
}
}
}

@tailrec
private def generateRec(exploredPaths: Vector[(Seq[Exp], Boolean)], result: BranchTree): BranchTree = { // result.instanceOf[Branch] must hold
exploredPaths.length match {
case 0 => result
case _ =>
val (expressions, isResultFatal) = exploredPaths.head
result.extend(expressions, isResultFatal)
generateRec(exploredPaths.tail, result)
}
}

def generate(exploredPaths: Vector[(Seq[Exp], Boolean)]): Option[BranchTree] = {
exploredPaths.length match {
case 0 => None
case _ =>
val (expressions, isResultFatal) = exploredPaths.head
val path = generatePathRec(expressions, if (isResultFatal) 1 else -1, Leaf) // -1 or distinguishing successful from no result at leaves
Some(generateRec(exploredPaths.tail, path))
}
}
}

object Leaf extends BranchTree
case class Branch(var exp : Exp,
var left: BranchTree,
var right: BranchTree,
protected[branchTree] var leftResFatalCount: Int,
protected[branchTree] var rightResFatalCount: Int) extends BranchTree {
def isLeftFatal : Boolean = leftResFatalCount > 0
def isRightFatal : Boolean = rightResFatalCount > 0
}
14 changes: 12 additions & 2 deletions src/main/scala/decider/Decider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import viper.silicon.state.terms.{Term, _}
import viper.silicon.utils.ast.{extractPTypeFromExp, simplifyVariableName}
import viper.silicon.verifier.{Verifier, VerifierComponent}
import viper.silver.ast
import viper.silver.ast.{LocalVarWithVersion, NoPosition}
import viper.silver.ast.{Exp, LocalVarWithVersion, NoPosition}
import viper.silver.components.StatefulComponent
import viper.silver.parser.{PKw, PPrimitiv, PReserved, PType}
import viper.silver.reporter.{ConfigurationConfirmation, InternalWarningMessage}
Expand Down Expand Up @@ -129,7 +129,7 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier =>
private var _proverOptions: Map[String, String] = Map.empty
private var _proverResetOptions: Map[String, String] = Map.empty
private val _debuggerAssumedTerms: mutable.Set[Term] = mutable.Set.empty

def functionDecls: Set[FunctionDecl] = _declaredFreshFunctions
def macroDecls: Vector[MacroDecl] = _declaredFreshMacros

Expand Down Expand Up @@ -256,6 +256,16 @@ trait DefaultDeciderProvider extends VerifierComponent { this: Verifier =>

def setPathConditionMark(): Mark = pathConditions.mark()

def getBranchConditionsExp(): Seq[Exp] = {
this.pcs.branchConditionExps.map(_._1)
.filterNot(e => e.isInstanceOf[viper.silver.ast.TrueLit]) /* remove "true" bcs introduced by viper.silicon.utils.ast.BigAnd */
.sortBy(_.pos match {
/* Order branchconditions according to source position */
case pos: viper.silver.ast.HasLineColumn => (pos.line, pos.column)
case _ => (-1, -1)
})
}

/* Assuming facts */

def startDebugSubExp(): Unit = {
Expand Down
13 changes: 12 additions & 1 deletion src/main/scala/decider/PathConditions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import viper.silicon.state.terms._
import viper.silicon.utils.Counter
import viper.silicon.verifier.Verifier
import viper.silver.ast
import viper.silver.ast.TrueLit
import viper.silver.ast.{Exp, TrueLit}
/*
* Interfaces
*/
Expand Down Expand Up @@ -61,6 +61,7 @@ trait RecordedPathConditions {

trait PathConditionStack extends RecordedPathConditions {
def setCurrentBranchCondition(condition: Term, conditionExp: (ast.Exp, Option[ast.Exp])): Unit
def getBranchConditionsExp(): Seq[Exp]
def add(assumption: Term): Unit
def addDefinition(assumption: Term): Unit
def add(declaration: Decl): Unit
Expand Down Expand Up @@ -459,6 +460,16 @@ private[decider] class LayeredPathConditionStack
layers.head.branchConditionExp = conditionExp
}

def getBranchConditionsExp(): Seq[Exp] = {
branchConditionExps.map(_._1)
.filterNot(e => e.isInstanceOf[viper.silver.ast.TrueLit]) /* remove "true" bcs introduced by viper.silicon.utils.ast.BigAnd */
.sortBy(_.pos match {
/* Order branchconditions according to source position */
case pos: viper.silver.ast.HasLineColumn => (pos.line, pos.column)
case _ => (-1, -1)
})
}

def startDebugSubExp(): Unit = {
layers.head.startDebugSubExp()
}
Expand Down
Loading